Skip to content

Instantly share code, notes, and snippets.

@chris-cadev
Last active December 20, 2024 22:51
Show Gist options
  • Save chris-cadev/5870f72c0b41ef9970bea88e582659ea to your computer and use it in GitHub Desktop.
Save chris-cadev/5870f72c0b41ef9970bea88e582659ea to your computer and use it in GitHub Desktop.
Tools to prune Git worktrees by status, age, or pattern with sorting, dry-run, and custom options.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <time.h>
#include <dirent.h>
#include <limits.h> // For PATH_MAX
#include <unistd.h>
// Define the maximum memory size to use (in bytes). Default is 1GB.
#define MAX_MEMORY_LIMIT (1 * 1024 * 1024 * 1024) // 1GB
// Structure to store worktree path and its corresponding age
typedef struct
{
char worktree_path[PATH_MAX];
long long age;
long long mtime;
} WorktreeInfo;
// Sorting options
typedef enum
{
SORT_BY_AGE_ASC,
SORT_BY_AGE_DESC,
SORT_BY_PATH_ASC,
SORT_BY_PATH_DESC
} SortOption;
char *read_file_in_chunks(const char *filename, size_t *out_size, size_t chunk_size)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
perror("Error opening file");
return NULL;
}
// Allocate an initial buffer for reading
size_t buffer_size = chunk_size;
char *buffer = (char *)malloc(buffer_size);
if (buffer == NULL)
{
perror("Error allocating memory");
fclose(file);
return NULL;
}
size_t total_bytes_read = 0;
size_t bytes_read;
while ((bytes_read = fread(buffer + total_bytes_read, 1, chunk_size, file)) > 0)
{
total_bytes_read += bytes_read;
// If the buffer is full, realloc to expand it
if (total_bytes_read + chunk_size > buffer_size)
{
buffer_size *= 2;
buffer = (char *)realloc(buffer, buffer_size);
if (buffer == NULL)
{
perror("Error reallocating memory");
fclose(file);
return NULL;
}
}
}
// Null-terminate the string and update out_size
buffer[total_bytes_read] = '\0';
*out_size = total_bytes_read;
fclose(file);
return buffer; // Return the dynamically allocated buffer
}
char *remove_git_suffix(char *path)
{
if (path == NULL)
{
return path;
}
// Find the length of the path
size_t len = strlen(path);
// Check if the path ends with "/.git"
if (len > 5 && strcmp(path + len - 5, "/.git") == 0)
{
// Null-terminate the string before "/.git"
path[len - 5] = '\0';
}
return path;
}
char *trim_whitespace(char *str)
{
if (str == NULL)
{
return NULL;
}
// Trim leading whitespace
while (isspace((unsigned char)*str))
{
str++;
}
// If the string is all whitespace, return an empty string
if (*str == '\0')
{
return str;
}
// Trim trailing whitespace
char *end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end))
{
end--;
}
// Null-terminate the string
*(end + 1) = '\0';
return str;
}
// Function to display the help message
void show_help(const char *program_name)
{
printf("Usage: %s <directory> [options]\n", program_name);
printf("\n");
printf("This program scans a directory for Git repositories and lists their worktrees with their age.\n");
printf("The output will show the time difference in terms of years, months, weeks, days, hours, minutes, and seconds.\n");
printf("\n");
printf("Example: %s /path/to/repo\n", program_name);
printf("Output: 1 y 2 mo 3 w 4 d 5 h ago /path/to/worktree\n");
printf("\n");
printf("Options:\n");
printf(" -h, --help Show this help message and exit\n");
printf(" --age-asc Sort by age (ascending)\n");
printf(" --age-desc Sort by age (descending)\n");
printf(" --path-asc Sort by path (alphabetical ascending)\n");
printf(" --path-desc Sort by path (alphabetical descending)\n");
printf(" -n <num> Limit the number of rows displayed\n");
printf(" --age-hidden Hide the age in the output\n");
printf(" --path-hidden Hide the path in the output\n");
printf("\n");
}
// Function to format time difference into a human-readable string with up to 2 factors
char *format_age(long long seconds, char *buffer)
{
long long years = seconds / 31536000;
seconds %= 31536000;
long long months = seconds / 2592000;
seconds %= 2592000;
long long weeks = seconds / 604800;
seconds %= 604800;
long long days = seconds / 86400;
seconds %= 86400;
long long hours = seconds / 3600;
seconds %= 3600;
long long minutes = seconds / 60;
long long remaining_seconds = seconds % 60;
buffer[0] = '\0'; // Clear the buffer
// Counter for the number of factors added
int factor_count = 0;
// Check if any of the time units are greater than zero and add them to the result
if (years > 0)
{
sprintf(buffer + strlen(buffer), "%lldy ", years);
factor_count++;
}
if (months > 0)
{
sprintf(buffer + strlen(buffer), "%lldmo ", months);
}
if (weeks > 0 && months == 0)
{
sprintf(buffer + strlen(buffer), "%lldw ", weeks);
}
if (days > 0)
{
sprintf(buffer + strlen(buffer), "%lldd ", days);
}
if (hours > 0)
{
sprintf(buffer + strlen(buffer), "%lldh ", hours);
}
if (minutes > 0)
{
sprintf(buffer + strlen(buffer), "%lldm ", minutes);
}
if (remaining_seconds > 0)
{
sprintf(buffer + strlen(buffer), "%llds ", remaining_seconds);
}
// Remove trailing space and add "ago" at the end
if (strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == ' ')
{
buffer[strlen(buffer) - 1] = '\0'; // Remove the trailing space
}
strcat(buffer, " ago");
return buffer;
}
char *format_mtime_elapsed(time_t mtime, char *buffer)
{
time_t current_time = time(NULL);
if (current_time == (time_t)-1)
{
perror("Error getting current time");
return NULL;
}
long long seconds = (long long)(current_time - mtime);
return format_age(seconds, buffer);
}
// Function to get the creation time of a worktree
long long get_worktree_age(const char *worktree_path)
{
struct stat worktree_stat;
if (stat(worktree_path, &worktree_stat) != 0)
{
perror("Error getting file stat");
return -1;
}
time_t current_time = time(NULL);
if (current_time == (time_t)-1)
{
perror("Error getting current time");
return -1;
}
return (long long)(current_time - worktree_stat.st_mtime); // Time in seconds
}
long long get_last_updated_time(const char *worktree_path)
{
struct stat worktree_stat;
if (stat(worktree_path, &worktree_stat) != 0)
{
perror("Error getting file stat");
return -1;
}
return (long long)worktree_stat.st_mtime; // Last modification time in seconds
}
// Comparison function to sort worktree information by age (ascending)
int compare_by_age_asc(const void *a, const void *b)
{
WorktreeInfo *worktree_a = (WorktreeInfo *)a;
WorktreeInfo *worktree_b = (WorktreeInfo *)b;
if (worktree_a->age < worktree_b->age)
return -1;
if (worktree_a->age > worktree_b->age)
return 1;
return 0;
}
// Comparison function to sort worktree information by age (descending)
int compare_by_age_desc(const void *a, const void *b)
{
return compare_by_age_asc(b, a); // Invert order
}
// Comparison function to sort worktree information by path (ascending)
int compare_by_path_asc(const void *a, const void *b)
{
WorktreeInfo *worktree_a = (WorktreeInfo *)a;
WorktreeInfo *worktree_b = (WorktreeInfo *)b;
return strcmp(worktree_a->worktree_path, worktree_b->worktree_path);
}
// Comparison function to sort worktree information by path (descending)
int compare_by_path_desc(const void *a, const void *b)
{
return compare_by_path_asc(b, a); // Invert order
}
// Function to scan for Git repositories in a directory and list worktrees
void scan_git_repos(const char *dir, SortOption sort_option, int limit, int hide_age, int hide_path)
{
struct dirent *entry;
DIR *dp = opendir(dir);
if (dp == NULL)
{
fprintf(stderr, "Error: Unable to open directory %s\n", dir);
return;
}
WorktreeInfo *worktrees = malloc(sizeof(WorktreeInfo) * 1024); // Allocate space for up to 1024 worktrees
size_t worktree_count = 0;
// Traverse through the directory
while ((entry = readdir(dp)) != NULL)
{
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
char git_dir[PATH_MAX];
snprintf(git_dir, sizeof(git_dir), "%s/%s/.git", dir, entry->d_name);
struct stat statbuf;
// Check if it's a Git repository
if (stat(git_dir, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
{
// Check for worktrees in the repository
char worktree_dir[PATH_MAX];
snprintf(worktree_dir, sizeof(worktree_dir), "%s/%s/.git/worktrees", dir, entry->d_name);
DIR *worktree_dp = opendir(worktree_dir);
if (worktree_dp)
{
struct dirent *worktree_entry;
while ((worktree_entry = readdir(worktree_dp)) != NULL)
{
// Skip . and ..
if (strcmp(worktree_entry->d_name, ".") == 0 || strcmp(worktree_entry->d_name, "..") == 0)
{
continue;
}
// Get the full path of the worktree
char worktree_path[PATH_MAX];
snprintf(worktree_path, sizeof(worktree_path), "%s/%s/.git/worktrees/%s", dir, entry->d_name, worktree_entry->d_name);
// Get the age of the worktree
long long age = get_worktree_age(worktree_path);
long long mtime = get_last_updated_time(worktree_path);
if (age >= 0)
{
size_t file_size = 0;
snprintf(worktree_path, sizeof(worktree_path), "%s/gitdir", worktree_path);
char *read_worktree = read_file_in_chunks(worktree_path, &file_size, 1024); // Read the file in 1KB chunks
strncpy(worktrees[worktree_count].worktree_path, remove_git_suffix(trim_whitespace(read_worktree)), PATH_MAX);
free(read_worktree);
worktrees[worktree_count].age = age;
worktrees[worktree_count].mtime = mtime;
worktree_count++;
// If the array exceeds the size, reallocate
if (worktree_count >= 1024)
{
worktrees = realloc(worktrees, sizeof(WorktreeInfo) * (worktree_count + 1024));
}
}
}
closedir(worktree_dp);
}
}
}
// Sort based on the chosen option
switch (sort_option)
{
case SORT_BY_AGE_ASC:
qsort(worktrees, worktree_count, sizeof(WorktreeInfo), compare_by_age_asc);
break;
case SORT_BY_AGE_DESC:
qsort(worktrees, worktree_count, sizeof(WorktreeInfo), compare_by_age_desc);
break;
case SORT_BY_PATH_ASC:
qsort(worktrees, worktree_count, sizeof(WorktreeInfo), compare_by_path_asc);
break;
case SORT_BY_PATH_DESC:
qsort(worktrees, worktree_count, sizeof(WorktreeInfo), compare_by_path_desc);
break;
}
// Display the sorted worktrees (limit the number of rows)
char age_buffer[100];
int row_count = 0;
for (size_t i = 0; i < worktree_count; i++)
{
if (limit > 0 && row_count >= limit)
break;
if (!hide_age)
{
printf("%-25s ", format_mtime_elapsed(worktrees[i].mtime, age_buffer));
}
if (!hide_path)
{
printf("%-40s\n", worktrees[i].worktree_path);
}
row_count++;
}
free(worktrees); // Free the allocated memory
closedir(dp);
}
// Function to check if a directory exists
int directory_exists(const char *path)
{
struct stat statbuf;
return (stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode));
}
// Main function with enhanced memory handling and directory scanning
int main(int argc, char *argv[])
{
// Check if help option is requested
if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0))
{
show_help(argv[0]);
return 0;
}
// Validate the number of arguments
if (argc < 2 || argc > 5)
{
fprintf(stderr, "Error: Invalid number of arguments.\n");
show_help(argv[0]);
return 1;
}
// Get the target directory
const char *target_dir = argv[1];
SortOption sort_option = SORT_BY_AGE_DESC; // Default sorting by age descending
int limit = -1; // No limit by default
int hide_age = 0;
int hide_path = 0;
// Parse sorting options and other flags
for (int i = 2; i < argc; i++)
{
if (strcmp(argv[i], "--age-asc") == 0)
{
sort_option = SORT_BY_AGE_ASC;
}
else if (strcmp(argv[i], "--age-desc") == 0)
{
sort_option = SORT_BY_AGE_DESC;
}
else if (strcmp(argv[i], "--path-asc") == 0)
{
sort_option = SORT_BY_PATH_ASC;
}
else if (strcmp(argv[i], "--path-desc") == 0)
{
sort_option = SORT_BY_PATH_DESC;
}
else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc)
{
limit = atoi(argv[i + 1]);
i++; // Skip the next argument
}
else if (strcmp(argv[i], "--age-hidden") == 0)
{
hide_age = 1;
}
else if (strcmp(argv[i], "--path-hidden") == 0)
{
hide_path = 1;
}
else
{
fprintf(stderr, "Error: Unknown option %s\n", argv[i]);
show_help(argv[0]);
return 1;
}
}
// Check if the directory exists
if (!directory_exists(target_dir))
{
fprintf(stderr, "Error: %s is not a valid directory.\n", target_dir);
return 1;
}
scan_git_repos(target_dir, sort_option, limit, hide_age, hide_path); // Start scanning the Git repositories
return 0;
}
# remove_repo_directory.sh (Module)
# Colors for output formatting
RESET="\033[0m"
RED="\033[31m"
YELLOW="\033[33m"
# Function to display error messages
print_error() {
echo -e "${RED}Error: $1${RESET}"
}
# Function to display dry-run messages
print_dry_run() {
echo -e "${YELLOW}Warning: $1${RESET}"
}
# Function to display action messages
print_action() {
echo -e "${RED}$1${RESET}"
}
# Function to validate the Git directory and retrieve the bare repository path
get_bare_repo() {
local dir=$1
local bare_repo
bare_repo=$(git -C "$dir" rev-parse --git-dir 2>/dev/null)
if [[ $? -ne 0 || -z "$bare_repo" ]]; then
print_error "Failed to find Git repository in $dir."
return 1
fi
# Remove trailing .git to get the base repository path
echo "${bare_repo%/.git}"
}
# Function to remove the specified directory from the Git worktree
remove_repo_directory() {
local dir_to_remove=$1
# Validate input directory
if [[ -z "$dir_to_remove" ]]; then
print_error "Directory to remove is not specified."
return 1
fi
# Get the bare repository path
local bare_repo
bare_repo=$(get_bare_repo "$dir_to_remove")
if [[ $? -ne 0 ]]; then
return 1
fi
# Handle dry-run case
if [[ $DRY_RUN == true ]]; then
print_dry_run "Would remove $dir_to_remove from $bare_repo"
return 0
fi
# Actual removal
print_action "Removing $dir_to_remove from $bare_repo"
git -C "$bare_repo" worktree remove -f "$dir_to_remove" 2>/dev/null
if [[ $? -ne 0 ]]; then
print_error "Failed to remove the worktree from $bare_repo."
return 1
fi
}
# Example usage
# Set DRY_RUN to true or false before calling the function
# DRY_RUN=true
# remove_repo_directory "/path/to/worktree"
#!/bin/bash
# Color codes
RESET="\033[0m"
RED="\033[31m"
YELLOW="\033[33m"
BLUE="\033[34m"
GREEN="\033[32m"
# Get the current working directory
CWD=$(dirname "$(realpath "$0")")
# Source the script to remove repository directory
source "$CWD/remove-repo-directory.mod.sh"
# Default value for dry-run (set to false by default)
DRY_RUN=false
# Function to display usage instructions
usage() {
echo -e "${YELLOW}Usage: $0 [-d] <directory_to_remove>${RESET}"
echo -e "${YELLOW} -d Dry-run mode (optional). If set, no actual removal will occur.${RESET}"
echo -e "${YELLOW} <directory_to_remove> The directory to remove from the Git worktree.${RESET}"
echo -e "${YELLOW}Example: $0 -d /path/to/directory${RESET}"
exit 0
}
# Parse command-line arguments
while getopts ":d" opt; do
case $opt in
d) DRY_RUN=true ;;
\?) usage ;;
esac
done
shift $((OPTIND - 1))
# Ensure a directory argument is provided
if [[ $# -ne 1 ]]; then
usage
fi
# Validate the directory argument
DIR_TO_REMOVE="$1"
# Logging the action
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}Dry-run mode enabled. No changes will be made.${RESET}"
else
echo -e "${BLUE}Executing removal for directory: $DIR_TO_REMOVE${RESET}"
fi
# Call the function to remove the repository directory
remove_repo_directory "$DIR_TO_REMOVE"
# Check if the removal was successful
if [[ $? -eq 0 ]]; then
echo -e "${GREEN}Successfully removed directory: $DIR_TO_REMOVE${RESET}"
else
echo -e "${RED}Error: Failed to remove directory '$DIR_TO_REMOVE'.${RESET}"
exit 1
fi
exit 0
#!/bin/bash
CWD=$(dirname $(realpath "$0"))
source "$CWD/remove-repo-directory.mod.sh"
# Define color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RESET='\033[0m'
# Configuration variables
WORKTREE_PATTERN="ENG-\d{5}" # Regex pattern to match worktree IDs (can be customized)
AGE_FILTER_PATTERN="[0-9]+(mo|y)" # Default regex for age filtering
MAX_DEPTH=1 # Max depth for the 'find' command to search subdirectories (adjustable)
DRY_RUN=false # If true, only prints directories to be deleted; does not delete them
DIRECTORY_TO_FIND="." # Directory where the command will crawl
# Helper function to display usage information
usage() {
echo -e "${CYAN}Usage: $0 [options]${RESET}"
echo "Options:"
echo -e " ${YELLOW}-d, --dry-run${RESET} Dry run mode (default: false), only prints directories to be deleted"
echo -e " ${YELLOW}-s, --source <directory>${RESET} Directory to search for worktrees (default: '$DIRECTORY_TO_FIND')"
echo -e " ${YELLOW}-p, --pattern <pattern>${RESET} Set custom regex pattern for worktree IDs (default: '$WORKTREE_PATTERN')"
echo -e " ${YELLOW}-a, --age-filter <pattern>${RESET} Set regex pattern for age filtering (default: '$AGE_FILTER_PATTERN')"
echo -e " ${YELLOW}-m, --max-depth <depth>${RESET} Set max depth for 'find' command (default: $MAX_DEPTH)"
echo -e " ${YELLOW}-h, --help${RESET} Display this help message"
echo
echo -e "${GREEN}Examples:${RESET}"
echo -e " $0 -d"
echo -e " $0 --dry-run --source ./worktrees --pattern 'DEV-\\d{4}'"
}
# Function to check if a directory exists
validate_directory() {
if [ ! -d "$1" ]; then
echo -e "${RED}Error: Directory '$1' does not exist or is not a valid directory.${RESET}"
exit 1
fi
}
# Process command-line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--dry-run)
DRY_RUN=true
shift
;;
-s|--source)
DIRECTORY_TO_FIND="$2"
validate_directory "$DIRECTORY_TO_FIND"
shift 2
;;
-p|--pattern)
WORKTREE_PATTERN="$2"
shift 2
;;
-m|--max-depth)
if [[ "$2" =~ ^[0-9]+$ ]]; then
MAX_DEPTH="$2"
shift 2
else
echo -e "${RED}Error: Max depth must be a positive integer.${RESET}"
exit 1
fi
;;
-h|--help)
usage
exit 0
;;
*)
echo -e "${RED}Error: Invalid option '$1'.${RESET}"
usage
exit 1
;;
esac
done
# Main logic to process worktrees and find directories to remove
process_worktrees() {
# Find all relevant worktrees in the specified directory
local dirs_to_remove=$( \
$CWD/list-worktrees "$DIRECTORY_TO_FIND" | \
grep -E "\dmo|\dy" | \
grep -oE "$WORKTREE_PATTERN" | \
sort | uniq | \
while read -r worktree_id; do \
find "$DIRECTORY_TO_FIND" -type d -name "*$worktree_id*" -maxdepth "$MAX_DEPTH"; \
done \
)
if [ -n "$dirs_to_remove" ]; then
for dir in $dirs_to_remove; do
# Check the status of the worktree
if work-status "$worktree_id" | grep -q "[Closed]"; then
remove_repo_directory "$dir"
fi
done
fi
$CWD/list-worktrees "$DIRECTORY_TO_FIND" | grep -E "\dmo|\dy" | rev | awk '{print $1}' | rev | while read -r worktree_dir; do
local directory_name=$(basename $worktree_dir)
local bare_repo=$(echo $directory_name | awk -F "_" '{print $1}')
local worktree_id=$(echo $worktree_dir | grep -oE "$WORKTREE_PATTERN")
local worktree_reference="$DIRECTORY_TO_FIND/$bare_repo/.git/worktrees/$directory_name"
if [[ -z "$worktree_id" ]]; then
echo -e "${YELLOW}Warning: Worktree ID not found for $worktree_dir"
elif work-status "$worktree_id" | grep -q "[Closed]"; then
if [[ $DRY_RUN == true ]]; then
echo -e "${YELLOW}Would remove $worktree_reference from $bare_repo"
else
echo -e "${RED}Removing $worktree_reference from $bare_repo"
rm -rf $worktree_reference
fi
fi
done
}
if $DRY_RUN; then
echo -e "${YELLOW}Dry run enabled. The following directories would be removed:${RESET}"
process_worktrees
echo -e "${GREEN}Dry run finshed!${RESET}"
else
process_worktrees
echo -e "${GREEN}Finshed!${RESET}"
fi

Worktree Pruner

This Gist contains tools to efficiently prune Git worktrees based on their statuses, ages, and patterns defined in a work-status file. The solution is implemented with both C (remove-worktrees.c) and Bash (remove-worktrees.sh), catering to diverse environments and preferences.


TL;DR
The Worktree Pruner is a set of tools in C and Bash for efficiently managing and pruning Git worktrees. It identifies stale or closed worktrees based on their statuses, ages, and patterns. Key features include sorting by age or path, dry-run support, and customizable search patterns.

  • C Tool (remove-worktrees.c):

    • Compiles with GCC.
    • Sorts worktrees by age or path and displays formatted output.
  • Bash Script (remove-worktrees.sh):

    • Automates the search, validation, and removal of worktrees.
    • Supports dry runs and regex matching for IDs (e.g., ENG-\d{5}).

These tools simplify workspace maintenance by ensuring only active worktrees remain.


Features

  • Efficient Git Worktree Management:
    • Identify and manage stale Git worktrees in a specified directory.
    • Remove worktrees whose statuses are marked as closed.
  • Sorting Options:
    • Sort by age or path (ascending/descending).
  • Dry Run Support:
    • Simulate deletions with a preview of directories to be deleted.
  • Customizable:
    • Adjust search patterns, directory depths, and display settings.

Contents

  1. remove-worktrees.c:
    A C program for analyzing worktree metadata, sorting them by age or path, and displaying them in a formatted way.

  2. remove-worktrees.sh:
    A Bash script to automate finding, validating, and removing Git worktrees based on regex patterns.

Usage

remove-worktrees.c

Compile the program with GCC:

gcc -o remove-worktrees remove-worktrees.c

Run the program:

./remove-worktrees [options]

Options

Option Description
-h, --help Show help message and usage instructions.
--age-asc Sort worktrees by age (ascending).
--age-desc Sort worktrees by age (descending, default).
--path-asc Sort worktrees by path (alphabetically ascending).
--path-desc Sort worktrees by path (alphabetically descending).
-n <num> Limit the number of rows displayed.
--age-hidden Hide the age column in the output.
--path-hidden Hide the path column in the output.

remove-worktrees.sh

Run the script:

bash remove-worktrees.sh [options]

Options

Option Description
-d, --dry-run Preview directories to be deleted (no changes made).
-s <directory> Specify the root directory to scan.
-p <pattern> Regex pattern to match worktree IDs (default: ENG-\d{5}).
-m <depth> Set the maximum depth for the search (default: 1).
-h, --help Show help message and usage instructions.

Example

To list Git worktrees by age in descending order:

./remove-worktrees ./hacks --age-desc

To simulate the deletion of worktrees in a dry run mode:

bash remove-worktrees.sh --dry-run --source ./worktrees

Notes

  • Dependencies:
    • remove-worktrees.c requires GCC for compilation.
    • remove-worktrees.sh uses standard Bash utilities like find and stat.
  • Ensure you have permissions to modify or delete files in the target directories.

Problem tried to solve

I created these scripts to help me efficiently prune worktrees based on their ticket status and age, specifically identifying those marked as closed in the work-status file. I typically store all my worktrees in a single directory, usually the one determined by the enterprise, to keep things organized and standardized. This approach simplifies maintenance and ensures I only keep active worktrees in my workspace.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment