Git: worktree creation and unlinking scripts
Source: Notion | Last edited: 2024-09-07 | ID: a34db094-bf6...
This tutorial will guide you through using two scripts to manage your Git worktrees effectively. The scripts will help you switch branches, create worktrees, and unlink worktrees. We’ll follow the 5W1H principle (Who, What, When, Where, Why, and How) to ensure clarity.
Motivation (Why)
Section titled “Motivation (Why)”The motivation behind using git worktree is to streamline the development process by allowing multiple branches to be worked on simultaneously within the same repository. This is particularly useful when you want to have multiple physical directories open in the same IDE (Cursor IDE or Visual Studio Code) to avoid switching windows back and forth. This approach provides a more coherent interface, making it easier to manage and compare different branches.
Managing multiple worktrees allows you to work on different branches simultaneously without switching contexts (or git checkout to another branch). These scripts automate the process, making it more efficient and less error-prone.
These scripts are intended for developers who use Git for version control and need to manage multiple worktrees within the same Workspace of Cursor or VS Code IDE efficiently.
- Script 1: Switches to a specified branch and creates a worktree for the current branch in the upper directory.
- Script 2: Unlinks a specified worktree and provides commands to navigate back to the main repository.
Use these scripts when you need to:
- Switch to a different branch and create a worktree for the current branch.
- Unlink an existing worktree and clean up your worktree management.
Scenario: Feature Development and Cleanup
Section titled “Scenario: Feature Development and Cleanup”Imagine you are a developer working on a new feature for a project. You are currently on the helpers branch and need to switch to the cost_adjusted_v2 branch to review some recent changes. However, you also want to continue working on the helpers branch without losing your current context. Using Script 1, you can switch to the cost_adjusted_v2 branch and create a worktree for the helpers branch in the upper directory, allowing you to work on both branches simultaneously. The perk of this setup is that, because there are two directories involved, you can take advantage of the file system structure to open both branches in the same workspace in your IDE (such as Visual Studio Code). This means you can have the cost_adjusted_v2 branch open in one folder and the helpers branch open in another folder within the same workspace, making it easy to switch between them and compare changes side-by-side. After completing your tasks on the helpers branch, you decide to clean up by unlinking the worktree. You use Script 2 to safely unlink the helpers worktree, ensuring that any uncommitted changes are either committed or stashed, and navigate back to the main repository. This workflow allows you to efficiently manage multiple branches and worktrees, reducing context-switching overhead and maintaining a clean working environment.
These scripts should be run in the terminal within the root directory of your Git repository.
Follow these steps to use the scripts:
Script 1: Switch Branch and Create Worktree
Section titled “Script 1: Switch Branch and Create Worktree”- Create the Script File
# Create a temporary script filetemp_script=$(mktemp)
# Write the script content to the temporary filecat << 'EOF' > "$temp_script"# Define color codesRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[0;33m'BLUE='\033[0;34m'CYAN='\033[0;36m'NC='\033[0m' # No Color
# Get the current branch namecurrent_branch=$(git rev-parse --abbrev-ref HEAD)printf "${BLUE}Current branch:${NC} %s\n" "$current_branch"
# Check for uncommitted changesprintf "${CYAN}Checking for uncommitted changes...${NC}\n"if ! git diff-index --quiet HEAD -- || [ -n "$(git ls-files --others --exclude-standard)" ]; then printf "${RED}Warning: There are uncommitted changes or untracked files in the current branch.${NC}\n" printf "${YELLOW}If you proceed, these changes will be lost.${NC}\n" read -p "Do you still want to switch branches? (yes/no): " confirm_uncommitted if [ "$confirm_uncommitted" != "yes" ]; then printf "${RED}Branch switching aborted due to uncommitted changes.${NC}\n" exit 1 fifi
# List all branchesbranches=($(git branch --format='%(refname:short)'))printf "${CYAN}Available branches:${NC}\n"for i in "${!branches[@]}"; do printf "${YELLOW}%d) %s${NC}\n" "$((i+1))" "${branches[$i]}"done
# Ask for the target branch to switch toread -p "Enter the number of the target branch to switch to: " branch_number
# Validate the inputif ! [[ "$branch_number" =~ ^[0-9]+$ ]] || [ "$branch_number" -lt 1 ] || [ "$branch_number" -gt "${#branches[@]}" ]; then printf "${RED}Invalid selection. Exiting.${NC}\n" exit 1fi
target_branch="${branches[$((branch_number-1))]}"printf "${CYAN}Switching to the target branch...${NC}\n"if git checkout "$target_branch"; then printf "${GREEN}Switched to branch %s.${NC}\n" "$target_branch"else printf "${RED}Failed to switch to branch %s.${NC}\n" "$target_branch" exit 1fi
# Create a meaningful worktree directory nametimestamp=$(date +%Y%m%d%H%M%S)upper_directory=$(dirname "$(pwd)")worktree_path="$upper_directory/${current_branch}_worktree_$timestamp"printf "${CYAN}Creating a worktree for branch %s in %s...${NC}\n" "$current_branch" "$worktree_path"if git worktree add "$worktree_path" "$current_branch"; then printf "${GREEN}Worktree created at %s for branch %s.${NC}\n" "$worktree_path" "$current_branch"else printf "${RED}Failed to create worktree at %s for branch %s.${NC}\n" "$worktree_path" "$current_branch" exit 1fi
# Display final summaryprintf "${CYAN}Summary of actions taken:${NC}\n"printf "${GREEN}1. Switched to branch %s.${NC}\n" "$target_branch"printf "${GREEN}2. Created worktree at %s for branch %s.${NC}\n" "$worktree_path" "$current_branch"
# Provide the cd command to navigate to the new worktree directoryprintf "${CYAN}To navigate to the new worktree directory, use the following command:${NC}\n"printf "${YELLOW}cd %s${NC}\n" "$worktree_path"EOF
# Make the temporary script executablechmod +x "$temp_script"
# Execute the temporary script"$temp_script"
# Remove the temporary scriptrm "$temp_script"- Run the Script
- Open your terminal and navigate to the root directory of your Git repository.
- Copy and paste the script into your terminal and press Enter.
- Follow the prompts to switch branches and create a worktree.
Specificity and Nuances of Script 1:
Section titled “Specificity and Nuances of Script 1:”- Branch Listing: The script lists all available branches and allows you to select one by entering its corresponding number.
- Uncommitted Changes Check: The script checks for uncommitted changes and warns you before proceeding.
- Worktree Naming: The worktree directory name includes the current branch name and a timestamp for uniqueness.
- Manual Navigation: The script provides a
cdcommand to manually navigate to the new worktree directory.
Script 2: Unlink Worktree
Section titled “Script 2: Unlink Worktree”- Create the Script File
# Create a temporary script filetemp_script=$(mktemp)
# Write the script content to the temporary filecat << 'EOF' > "$temp_script"# Define color codesRED='\033[0;31m'GREEN='\033[0;32m'YELLOW='\033[0;33m'BLUE='\033[0;34m'CYAN='\033[0;36m'PURPLE='\033[0;35m'NC='\033[0m' # No Color
# Get the list of worktreesprintf "${CYAN}Getting the list of worktrees...${NC}\n"worktree_list=$(git worktree list)printf "${BLUE}Worktree list:${NC}\n"printf "%s\n" "$worktree_list"
# Extract the current worktree pathcurrent_worktree=$(pwd)printf "${BLUE}Current worktree:${NC} %s\n" "$current_worktree"
# Extract the main repository pathmain_repo_path=$(echo "$worktree_list" | grep -v "$current_worktree" | awk '{print $1}' | head -n 1)printf "${BLUE}Main repository path:${NC} %s\n" "$main_repo_path"
# Check if the main repository path is foundif [ -z "$main_repo_path" ]; then printf "${RED}Main repository path not found.${NC}\n" exit 1fi
# Find the branch or commit associated with the current worktreeoriginating_branch=$(echo "$worktree_list" | grep "$current_worktree" | awk '{print $2}' | sed 's/\[//;s/\]//')printf "${BLUE}Originating branch or commit:${NC} %s\n" "$originating_branch"
# Check if the originating branch is a commit hash and try to get the branch nameif [[ "$originating_branch" =~ ^[0-9a-f]{7,40}$ ]]; then printf "${YELLOW}Originating branch is a commit hash. Trying to resolve to branch name...${NC}\n" # Check if the commit is part of any local branches branch_name=$(git branch --contains "$originating_branch" | grep -v 'remotes/' | head -n 1 | sed 's/* //') if [ -z "$branch_name" ]; then # If no local branch contains the commit, fall back to name-rev branch_name=$(git name-rev --name-only "$originating_branch") fi printf "${YELLOW}Resolved branch name:${NC} %s\n" "$branch_name" if [ -n "$branch_name" ]; then originating_branch=$branch_name fifi
# Print the originating branchprintf "${BLUE}The originating branch for the current worktree is:${NC} %s\n" "$originating_branch"
# Check for uncommitted changesprintf "${CYAN}Checking for uncommitted changes...${NC}\n"if ! git diff-index --quiet HEAD -- || [ -n "$(git ls-files --others --exclude-standard)" ]; then printf "${RED}Warning: There are uncommitted changes or untracked files in the worktree.${NC}\n" printf "${YELLOW}If you proceed, these changes will be lost.${NC}\n" read -p "Do you still want to unlink the worktree? (yes/no): " confirm_uncommitted if [ "$confirm_uncommitted" != "yes" ]; then printf "${RED}Worktree unlinking aborted due to uncommitted changes.${NC}\n" exit 1 fifi
# Find the worktree IDworktree_id=$(basename "$current_worktree")worktree_config_path="$main_repo_path/.git/worktrees/$worktree_id"
# Check if the worktree config path existsif [ ! -d "$worktree_config_path" ]; then printf "${RED}Worktree config path not found: %s${NC}\n" "$worktree_config_path" exit 1fi
# Detailed warning about unlinking consequencesprintf "${YELLOW}Warning: Unlinking the worktree will have the following consequences:${NC}\n"printf "${YELLOW}1. The worktree will be unlinked from Git's management.${NC}\n"printf "${YELLOW}2. The worktree directory and its contents will remain intact.${NC}\n"printf "${YELLOW}3. Any uncommitted changes in the worktree will be lost.${NC}\n"printf "${YELLOW}4. Re-linking the worktree later is not straightforward. You will need to delete the existing directory and create a new worktree.${NC}\n"printf "${YELLOW}5. You cannot create a worktree for a branch that is currently checked out in the main repository. You need to switch to a different branch in the main repository before creating a worktree for the original branch.${NC}\n"printf "${YELLOW}6. Ensure you have committed or stashed any important changes before proceeding.${NC}\n"
# Ask for confirmation to unlink the current worktreeread -p "Do you want to unlink the worktree at $current_worktree? (yes/no): " confirm
if [ "$confirm" = "yes" ]; then # Unlink the worktree printf "${CYAN}Unlinking the worktree...${NC}\n" rm -rf "$worktree_config_path" if [ $? -eq 0 ]; then printf "${GREEN}Worktree at %s has been unlinked.${NC}\n" "$current_worktree"
# Navigate to the main repository printf "${CYAN}Navigating to the main repository...${NC}\n" cd "$main_repo_path" || exit printf "${BLUE}Navigated to main repository:${NC} %s\n" "$(pwd)"
# Display final summary printf "${CYAN}Summary of actions taken:${NC}\n" printf "${GREEN}1. Worktree at %s has been unlinked.${NC}\n" "$current_worktree" printf "${GREEN}2. Navigated to main repository at %s.${NC}\n" "$main_repo_path" printf "${PURPLE}To navigate back to the main repository and switch to the originating branch, run the following commands:${NC}\n" printf "${PURPLE}cd %s${NC}\n" "$main_repo_path" printf "${PURPLE}git checkout %s${NC}\n" "$originating_branch" else printf "${RED}Failed to unlink the worktree at %s.${NC}\n" "$current_worktree" exit 1 fielse printf "${YELLOW}Worktree unlinking aborted.${NC}\n"fiEOF
# Make the temporary script executablechmod +x "$temp_script"
# Execute the temporary script"$temp_script"
# Remove the temporary scriptrm "$temp_script"- Run the Script
- Open your terminal and navigate to the root directory of your Git repository.
- Copy and paste the script into your terminal and press Enter.
- Follow the prompts to unlink the worktree.
Specificity and Nuances of Script 2:
Section titled “Specificity and Nuances of Script 2:”- Worktree Listing: The script lists all existing worktrees and identifies the current worktree.
- Uncommitted Changes Check: The script checks for uncommitted changes and warns you before proceeding.
- Unlinking Consequences: The script provides detailed warnings about the consequences of unlinking a worktree.
- Manual Navigation: The script provides commands to navigate back to the main repository and switch to the originating branch.
Risk Mitigation and Considerations
Section titled “Risk Mitigation and Considerations”Script 1:
Section titled “Script 1:”- Uncommitted Changes: The script checks for uncommitted changes and warns you, giving you the option to abort the operation to prevent data loss.
- Branch Selection: The script lists all branches and allows you to select one by number, reducing the risk of typos.
- Worktree Naming: The worktree directory name includes a timestamp, ensuring uniqueness and preventing conflicts.
Script 2:
Section titled “Script 2:”- Uncommitted Changes: The script checks for uncommitted changes and warns you, giving you the option to abort the operation to prevent data loss.
- Worktree Identification: The script identifies the current worktree and provides detailed warnings about the consequences of unlinking it.
- Manual Navigation: The script provides commands to manually navigate back to the main repository and switch branches, ensuring you are aware of the changes being made.
Summary
Section titled “Summary”By following these steps, you can efficiently manage your Git worktrees using the provided scripts. The first script helps you switch branches and create worktrees, while the second script helps you unlink worktrees and navigate back to the main repository. The scripts include checks for uncommitted changes, detailed warnings, and provide manual navigation commands to ensure a smooth workflow.