A safe, intelligent Git script that merges origin/main into remote feature branches using worktreesβso your current work is never disrupted.
π― Why Use This Script?
The Problem It Solves
When working on feature branches, you often need to merge the latest changes from main to:
- Keep your branch up-to-date
- Resolve conflicts early
- Ensure CI/CD pipelines pass with latest dependencies
Traditional approach problems:
git checkout feature-branch
git merge main
# β Your working directory changes
# β Uncommitted work gets in the way
# β Conflicts force you to stop everything
# β Multiple branches = lots of manual switching
How This Script Helps
β
Non-Disruptive β Uses Git worktrees, never touches your current branch
β
Safe β Detects conflicts and guides you through resolution
β
Intelligent β Auto-selects when only one candidate branch exists
β
Flexible β Supports dry-run mode, branch exclusions, batch operations
β
Clean β Automatically removes worktrees and temporary branches when done
β
User-Friendly β Clear prompts, helpful error messages, conflict resolution guides
π¦ Installation
Option 1: ~/.local/bin (Recommended – XDG Standard)
The ~/.local/bin directory follows the XDG Base Directory specification and is the modern standard for user-local executables. Most Linux distributions automatically include it in PATH.
# 1. Create the directory (if it doesn't exist)
mkdir -p ~/.local/bin
# 2. Add to PATH if needed (most modern systems already include it)
# Check if it's already in your PATH:
echo $PATH | grep -q "$HOME/.local/bin" && echo "Already in PATH" || echo "Need to add to PATH"
# If you need to add it manually:
# For bash:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# For zsh:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# For fish:
fish_add_path ~/.local/bin
# 3. Install the script
curl -o ~/.local/bin/merge-main https://raw.githubusercontent.com/yourusername/yourrepo/main/merge-main-into-remote-branch.sh
chmod +x ~/.local/bin/merge-main
# Or if you already have the file locally:
cp merge-main-into-remote-branch.sh ~/.local/bin/merge-main
chmod +x ~/.local/bin/merge-main
Why ~/.local/bin?
- β XDG Base Directory standard (widely adopted)
- β Often already in PATH on modern Linux distributions
- β Keeps user binaries separate from system binaries
- β Respects the filesystem hierarchy standard
- β Works seamlessly with systemd user services
Option 2: ~/bin (Alternative)
Traditional alternative, still widely used.
# Create a personal bin directory
mkdir -p ~/bin
# Add to PATH (usually required)
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Install the script
cp merge-main-into-remote-branch.sh ~/bin/merge-main
chmod +x ~/bin/merge-main
Option 3: System-Wide Installation
Requires sudo but makes it available to all users.
# Make executable and move to system bin
chmod +x merge-main-into-remote-branch.sh
sudo mv merge-main-into-remote-branch.sh /usr/local/bin/merge-main
Option 4: Shell Alias
Keep the script in your project and create an alias.
# Add to ~/.bashrc or ~/.zshrc
echo 'alias merge-main="/full/path/to/merge-main-into-remote-branch.sh"' >> ~/.bashrc
source ~/.bashrc
Verify Installation
# Check if the command is found
which merge-main
# Test with dry-run
DRY_RUN=1 merge-main
π Usage
Basic Syntax
merge-main [module] [options]
Parameters:
moduleβ (optional) Path to the Git repository folder- If omitted and current directory is a Git repo, uses it automatically
- Use
.to explicitly use the current directory - Otherwise, prompts for module name
Quick Examples
# Run in current directory (if it's a git repo)
merge-main
# Explicitly use current directory
merge-main .
# Specify a repository path
merge-main ~/projects/my-app
merge-main ./backend
# Preview without making changes
DRY_RUN=1 merge-main
# Exclude additional branches
EXCLUDE=develop,staging,release merge-main
# Combine options
DRY_RUN=1 EXCLUDE=hotfix,production merge-main ~/projects/api
ποΈ Environment Variables
DRY_RUN
Preview all Git commands without executing them.
# See what would happen without making changes
DRY_RUN=1 merge-main
# Output example:
# [dry-run] git fetch --prune origin
# [dry-run] git fetch origin main feature/user-auth
# [dry-run] git worktree add -b feature/user-auth ...
When to use:
- Testing the script for the first time
- Verifying which branch will be selected
- Checking excluded branches
- Understanding the workflow before committing
EXCLUDE
Comma-separated list of branch names to exclude from candidate selection.
# Default exclusions: develop, staging
# Also always excluded: main, HEAD
# Add more exclusions
EXCLUDE=develop,staging,hotfix,production merge-main
# Override defaults (only exclude release)
EXCLUDE=release merge-main
# No extra exclusions (only main and HEAD)
EXCLUDE= merge-main
Common use cases:
EXCLUDE=hotfixβ Avoid merging into emergency fix branchesEXCLUDE=production,releaseβ Skip protected deployment branchesEXCLUDE=archive/*β Skip archived branches
π Detailed Workflow
Step-by-Step Process
Repository Discovery
- Uses specified path or auto-detects current directory
- Validates it’s a Git repository
Fetch Latest State
- Runs
git fetch --prune originto sync remote branches - Ensures you’re working with up-to-date information
- Runs
Branch Selection
- Lists all remote branches except
main,HEAD, and excluded branches - Auto-selects if only one candidate exists
- Shows numbered menu if multiple candidates exist
- Lists all remote branches except
Worktree Setup
- Creates a temporary worktree in a safe location
- Checks out the target branch in the worktree
- Your current working directory remains untouched
Merge Execution
- Merges
origin/maininto the target branch - Creates a timestamped commit message
- Merges
Outcome Handling
- Success: Pushes to origin, cleans up worktree and temp branch
- Conflict: Shows conflicting files, provides resolution guide
π― Real-World Scenarios
Scenario 1: Single Feature Branch
You have one active feature branch that needs the latest main.
$ merge-main
==> No module specified β using current directory as repo
==> Fetching origin for '.'...
==> Fetching origin/main and origin/feature/user-auth...
==> Auto-selected branch: feature/user-auth
==> Creating worktree at '/tmp/_worktree-feature-user-auth' tracking origin/feature/user-auth
==> Merging origin/main into feature/user-auth...
==> Merge successful. Pushing feature/user-auth to origin...
==> Cleaning up worktree and local branch...
OK: merged main -> feature/user-auth in '.' at 10Apr2026 1430
Scenario 2: Multiple Branches
You have several feature branches and need to choose which one to update.
$ merge-main ~/projects/api
Multiple candidate branches found:
[1] feature/authentication
[2] feature/payment-gateway
[3] bugfix/timeout-issue
Pick one [1-3]: 2
==> Fetching origin/main and origin/feature/payment-gateway...
==> Creating worktree...
==> Merging origin/main into feature/payment-gateway...
==> Merge successful. Pushing feature/payment-gateway to origin...
OK: merged main -> feature/payment-gateway in 'api' at 10Apr2026 1445
Scenario 3: Merge Conflict
The merge encounters conflicts that need manual resolution.
$ merge-main
==> Merging origin/main into feature/new-ui...
!! Merge conflict detected. Conflicting files:
- src/components/Header.jsx
- src/styles/theme.css
Resolve conflicts manually, then push:
Step 1 β Go to the worktree where the conflict lives:
cd /tmp/_worktree-feature-new-ui
Step 2 β See the full status:
git status
Step 3 β Open each conflicting file and resolve the markers:
<<<<<<< HEAD β your branch (feature/new-ui)
your code
=======
incoming code
>>>>>>> origin/main β what came from main
Step 4 β Mark each resolved file as done:
git add <file>
Step 5 β Complete the merge commit:
git commit
Step 6 β Push the resolved branch:
git push -u origin feature/new-ui
Step 7 β Clean up the worktree:
cd -
git worktree remove /tmp/_worktree-feature-new-ui
git branch -D feature/new-ui
Resolution Example:
# Navigate to worktree
cd /tmp/_worktree-feature-new-ui
# Edit conflicting files in your editor
vim src/components/Header.jsx
# After resolving conflicts
git add src/components/Header.jsx src/styles/theme.css
git commit
git push -u origin feature/new-ui
# Return to original directory and clean up
cd -
git worktree remove /tmp/_worktree-feature-new-ui
git branch -D feature/new-ui
Scenario 4: Batch Processing
Update all feature branches across multiple repositories.
# Process all git repos in subdirectories
for dir in */; do
if [[ -d "$dir/.git" ]]; then
echo "Processing $dir..."
merge-main "${dir%/}"
fi
done
Example Output:
Processing api/...
OK: merged main -> feature/v2-endpoints in 'api' at 10Apr2026 1500
Processing frontend/...
OK: merged main -> feature/dashboard in 'frontend' at 10Apr2026 1502
Processing workers/...
!! Merge conflict detected in 'workers'...
Scenario 5: Dry Run Before Production
Test what will happen before making actual changes.
# Preview the entire workflow
DRY_RUN=1 merge-main
[dry-run] git fetch --prune origin
[dry-run] git fetch origin main feature/critical-fix
[dry-run] git worktree add -b feature/critical-fix /tmp/_worktree-feature-critical-fix origin/feature/critical-fix
[dry-run] git merge origin/main -m "merge: main into feature/critical-fix 10Apr2026 1515"
[dry-run] git push -u origin feature/critical-fix
[dry-run] git worktree remove /tmp/_worktree-feature-critical-fix
# If it looks good, run for real
merge-main
π‘οΈ Safety Features
1. Worktree Isolation
Your current branch and working directory are never modified. All merge operations happen in a temporary worktree.
Your Repo Temporary Worktree
--------- ------------------
main β you're here feature/xyz β merge happens here
feature/xyz (remote)
2. Stale Worktree Detection
If a previous run was interrupted (conflict, crash, manual abort), the script detects leftover worktrees and handles them intelligently.
With unresolved conflicts:
!! A worktree already exists at: /tmp/_worktree-feature-xyz
WARNING: These files still have unresolved merge conflicts:
- src/app.js
- config/settings.py
Force-removing this worktree will PERMANENTLY DISCARD those changes.
Force remove and start fresh? [y/N]
With uncommitted changes:
!! A worktree already exists at: /tmp/_worktree-feature-xyz
WARNING: The worktree has uncommitted changes:
M src/utils.js
A tests/new-test.js
Force-removing this worktree will PERMANENTLY DISCARD those changes.
Force remove and start fresh? [y/N]
Clean worktree (no changes):
==> Stale worktree has no pending changes β removing automatically
3. Branch Validation
Automatically excludes:
origin/HEAD(symbolic reference)origin/main(the source branch)- Branches in
EXCLUDElist (default:develop,staging)
4. Graceful Conflict Handling
Instead of leaving you in a broken state, the script:
- Clearly lists which files have conflicts
- Provides step-by-step resolution instructions
- Preserves the worktree so you can fix conflicts manually
- Exits with status code 1 (won’t silently fail in scripts)
π§ Advanced Usage
Custom Commit Message Pattern
Edit the script to customize the commit message:
# Find this line in the script (around line 163):
MSG="merge: main into ${BRANCH} ${STAMP}"
# Change to your preferred format:
MSG="chore: sync ${BRANCH} with main (${STAMP})"
MSG="Merge main β ${BRANCH} | ${STAMP}"
MSG="π main β ${BRANCH}"
Integration with CI/CD
Use in automation pipelines:
#!/bin/bash
# .github/workflows/sync-branches.sh
set -e
# Fail fast if merge has conflicts
merge-main || {
echo "Merge conflict detected - manual intervention required"
exit 1
}
# Continue with tests, deployments, etc.
Custom Exclusion Lists per Project
Create a wrapper script:
#!/bin/bash
# sync-api-branches.sh
# Project-specific exclusions
export EXCLUDE="hotfix,production,release,v1-stable"
merge-main ~/projects/api
Monitoring in Scripts
Capture output for logging:
LOG_FILE="merge-$(date +%Y%m%d).log"
merge-main 2>&1 | tee -a "$LOG_FILE"
if [[ ${PIPESTATUS[0]} -eq 0 ]]; then
echo "β Merge completed successfully" >> "$LOG_FILE"
else
echo "β Merge failed - check log" >> "$LOG_FILE"
# Send alert, create ticket, etc.
fi
β FAQ
Q: What if I accidentally run this on main itself?
The script explicitly excludes main from the candidate list, so it won’t let you merge main into main.
Q: Can I use this with GitHub/GitLab protected branches?
Yes, but you need push permissions. If the target branch is protected, the push step will fail with a permission error. The merge itself happens locally in the worktree.
Q: What happens to my uncommitted changes?
Nothingβthey remain untouched. The script works in a separate worktree, not your current directory.
Q: Can I merge into multiple branches at once?
Not directly, but you can script it:
for branch in feature/auth feature/payments feature/notifications; do
echo "Processing $branch..."
# Script will auto-select if you filter candidates to match exactly one
EXCLUDE="$(git branch -r | sed 's|origin/||' | grep -v "$branch" | tr '\n' ',')" merge-main
done
Q: How do I abort a merge in progress?
# Navigate to the worktree
cd /tmp/_worktree-your-branch
# Abort the merge
git merge --abort
# Return and clean up
cd -
git worktree remove /tmp/_worktree-your-branch
git branch -D your-branch
Q: Does this work with submodules?
Yes, but each submodule is a separate Git repository. You’d need to run the script separately for each submodule, or create a wrapper that iterates through them.
π Troubleshooting
“command not found: merge-main”
Problem: Script is not in PATH or not executable.
Solution:
# Check if it exists
ls -la ~/.local/bin/merge-main
# Make sure it's executable
chmod +x ~/.local/bin/merge-main
# Verify PATH includes ~/.local/bin
echo $PATH | grep "$HOME/.local/bin"
# If not in PATH, add it:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Reload shell config
source ~/.bashrc # or ~/.zshrc
“not a git repository”
Problem: Running in a directory that isn’t a Git repo.
Solution:
# Check if current dir is a git repo
git status
# Or specify the repo path explicitly
merge-main /path/to/your/repo
“no non-main remote branch found”
Problem: All branches are excluded or only main exists.
Solution:
# Check what branches exist
git branch -r
# Adjust exclusions
EXCLUDE= merge-main # Remove default exclusions
# Or create a feature branch first
git checkout -b feature/new-feature
git push -u origin feature/new-feature
Worktree conflicts with existing directory
Problem: /tmp/_worktree-branch-name already exists.
Solution:
# The script should handle this automatically
# If it doesn't, manually remove:
git worktree remove --force /tmp/_worktree-branch-name
# Then re-run the script
merge-main
π Script Reference
Full Script
#!/usr/bin/env bash
# =============================================================================
# merge-main-into-remote-branch.sh
#
# PURPOSE:
# Merges origin/main into a non-main remote branch inside a git repo (module).
# Uses git worktree so your current working branch is never touched.
#
# USAGE:
# ./merge-main-into-remote-branch.sh [module]
#
# module β (optional) path to the git repo folder.
# If omitted, the script checks if the current directory is a git
# repo and uses it automatically, otherwise it prompts.
# Pass "." to explicitly use the current directory.
#
# OPTIONS (environment variables):
# DRY_RUN=1 β Preview all git commands without executing them.
# EXCLUDE=a,b,c β Comma-separated branch names to exclude from candidates
# in addition to main and HEAD (default: "develop,staging").
#
# EXAMPLES:
# ./merge-main-into-remote-branch.sh managebac
# ./merge-main-into-remote-branch.sh .
# DRY_RUN=1 ./merge-main-into-remote-branch.sh managebac
# EXCLUDE=develop,staging,release ./merge-main-into-remote-branch.sh managebac
#
# # Run across ALL subdirectories that are git repos:
# for dir in */; do
# [[ -d "$dir/.git" ]] && ./merge-main-into-remote-branch.sh "${dir%/}"
# done
# =============================================================================
# NOTE: We intentionally do NOT use `set -e` globally here.
# The merge step is expected to exit non-zero on conflicts, and we need to
# handle that gracefully ourselves rather than letting bash abort the script.
# We keep -u (undefined variable check) and -o pipefail for safety.
set -uo pipefail
# -----------------------------------------------------------------------------
# CONFIG
# -----------------------------------------------------------------------------
# Dry-run mode: set DRY_RUN=1 to preview without making any changes
DRY_RUN="${DRY_RUN:-0}"
# Branches to always exclude from candidate list (on top of main and HEAD)
EXCLUDE="${EXCLUDE:-develop,staging}"
# -----------------------------------------------------------------------------
# HELPERS
# -----------------------------------------------------------------------------
# Print a timestamped info message
info() { echo "==> $*"; }
# Print an error to stderr and exit immediately
die() { echo "error: $*" >&2; exit 1; }
# Run a command normally, or just print it when DRY_RUN=1
run() {
if [[ "$DRY_RUN" == "1" ]]; then
echo "[dry-run] $*"
else
"$@"
fi
}
# Ask a yes/no question β returns 0 for yes, 1 for no
# Usage: confirm "Are you sure?" && do_something
confirm() {
local prompt="${1:-Are you sure?}"
local reply
read -rp "$prompt [y/N] " reply
[[ "${reply,,}" == "y" || "${reply,,}" == "yes" ]]
}
# -----------------------------------------------------------------------------
# RESOLVE MODULE / REPO PATH
# -----------------------------------------------------------------------------
MODULE="${1:-}"
# If no argument given, check if the current directory is already a git repo
if [[ -z "$MODULE" ]]; then
if git rev-parse --git-dir > /dev/null 2>&1; then
info "No module specified β using current directory as repo"
MODULE="."
else
read -rp "Module name (e.g. managebac) or '.' for current dir: " MODULE
fi
fi
[[ -z "$MODULE" ]] && die "module name required"
[[ -d "$MODULE" ]] || die "'$MODULE' is not a directory"
# Move into the module directory
cd "$MODULE"
# Confirm this is actually a git repository
git rev-parse --git-dir > /dev/null 2>&1 || die "'$MODULE' is not a git repository"
# -----------------------------------------------------------------------------
# FETCH LATEST REMOTE STATE
# -----------------------------------------------------------------------------
info "Fetching origin for '$MODULE'..."
run git fetch --prune origin
# -----------------------------------------------------------------------------
# BUILD EXCLUDE PATTERN
# -----------------------------------------------------------------------------
# Convert comma-separated EXCLUDE into a regex alternation e.g. "develop|staging"
EXCLUDE_PATTERN=$(echo "$EXCLUDE" | tr ',' '|')
# -----------------------------------------------------------------------------
# DISCOVER CANDIDATE BRANCHES
# -----------------------------------------------------------------------------
# List all remote branches, strip whitespace, filter out:
# - origin/HEAD β just a symbolic pointer, not a real branch
# - origin/main β this is the source, never the target
# - EXCLUDE list β e.g. develop, staging
mapfile -t CANDIDATES < <(
git branch -r \
| sed 's/^[[:space:]]*//' \
| grep -v '^origin/HEAD' \
| grep -v '^origin/main$' \
| grep -vE "^origin/(${EXCLUDE_PATTERN})$" \
| sed 's|^origin/||'
)
# -----------------------------------------------------------------------------
# SELECT TARGET BRANCH
# -----------------------------------------------------------------------------
if [[ ${#CANDIDATES[@]} -eq 0 ]]; then
die "no non-main remote branch found in '$MODULE' (excluded: main, $EXCLUDE)"
elif [[ ${#CANDIDATES[@]} -eq 1 ]]; then
# Only one candidate β select it automatically, no prompt needed
BRANCH="${CANDIDATES[0]}"
info "Auto-selected branch: $BRANCH"
else
# Multiple candidates β present a numbered menu and let the user choose
echo "Multiple candidate branches found:"
for i in "${!CANDIDATES[@]}"; do
printf " [%d] %s\n" "$((i+1))" "${CANDIDATES[$i]}"
done
read -rp "Pick one [1-${#CANDIDATES[@]}]: " PICK
[[ "$PICK" =~ ^[0-9]+$ ]] || die "invalid pick: '$PICK'"
(( PICK >= 1 && PICK <= ${#CANDIDATES[@]} )) || die "out of range: $PICK"
BRANCH="${CANDIDATES[$((PICK-1))]}"
fi
# -----------------------------------------------------------------------------
# PREPARE WORKTREE PATH & COMMIT MESSAGE
# -----------------------------------------------------------------------------
# Sanitize branch name: lowercase, replace slashes and spaces with dashes
SAFE_BRANCH="$(echo "$BRANCH" | tr '[:upper:]/ ' '[:lower:]-')"
if [[ "$MODULE" == "." ]]; then
# Running inside the repo itself β place worktree in /tmp to avoid nesting
WORKTREE="/tmp/_worktree-${SAFE_BRANCH}"
else
# Place worktree as a sibling of the module directory
WORKTREE="../_${MODULE}-${SAFE_BRANCH}"
fi
STAMP="$(date +'%d%b%Y %H%M')"
MSG="merge: main into ${BRANCH} ${STAMP}"
# -----------------------------------------------------------------------------
# FETCH MAIN + TARGET BRANCH (ensure both are up to date)
# -----------------------------------------------------------------------------
info "Fetching origin/main and origin/$BRANCH..."
run git fetch origin main "$BRANCH"
# -----------------------------------------------------------------------------
# HANDLE STALE WORKTREE FROM A PREVIOUS FAILED RUN
# -----------------------------------------------------------------------------
# `git worktree list` prints absolute paths β use grep -F (fixed string, no regex)
# to safely match the exact path without worrying about special characters
if git worktree list | grep -qF "$WORKTREE"; then
echo ""
echo "!! A worktree already exists at: $WORKTREE"
echo " This is likely left over from a previous run that had conflicts."
echo ""
# Check for files that are still in an unresolved conflict state (diff-filter=U)
UNMERGED=$(git -C "$WORKTREE" diff --name-only --diff-filter=U 2>/dev/null || true)
# Check for any other uncommitted changes (staged, unstaged, untracked)
UNCOMMITTED=$(git -C "$WORKTREE" status --porcelain 2>/dev/null || true)
if [[ -n "$UNMERGED" ]]; then
# Unresolved conflict markers still present β highest risk, warn loudly
echo " WARNING: These files still have unresolved merge conflicts:"
echo "$UNMERGED" | sed 's/^/ - /'
echo ""
echo " Force-removing this worktree will PERMANENTLY DISCARD those changes."
echo ""
confirm " Force remove and start fresh?" \
|| die "aborted β resolve conflicts manually in $WORKTREE then re-run"
elif [[ -n "$UNCOMMITTED" ]]; then
# Changes exist but no conflict markers β could be partial manual fixes
echo " WARNING: The worktree has uncommitted changes:"
echo "$UNCOMMITTED" | sed 's/^/ /'
echo ""
echo " Force-removing this worktree will PERMANENTLY DISCARD those changes."
echo ""
confirm " Force remove and start fresh?" \
|| die "aborted β inspect $WORKTREE before proceeding"
else
# Worktree is clean β safe to remove silently without asking
info "Stale worktree has no pending changes β removing automatically"
fi
run git worktree remove --force "$WORKTREE"
fi
# Remove any local tracking branch left over from a prior run.
# Try safe delete (-d) first; only force-delete (-D) if git considers it unmerged.
if git branch --list "$BRANCH" | grep -q .; then
git branch -d "$BRANCH" 2>/dev/null \
|| git branch -D "$BRANCH" 2>/dev/null \
|| true
fi
# -----------------------------------------------------------------------------
# CREATE FRESH WORKTREE TRACKING THE TARGET BRANCH
# -----------------------------------------------------------------------------
info "Creating worktree at '$WORKTREE' tracking origin/$BRANCH"
run git worktree add -b "$BRANCH" "$WORKTREE" "origin/$BRANCH"
# -----------------------------------------------------------------------------
# MERGE origin/main INTO TARGET BRANCH
# -----------------------------------------------------------------------------
info "Merging origin/main into $BRANCH..."
# Temporarily disable pipefail around the merge command.
# git merge exits non-zero on conflicts β that is expected and handled below.
# Without this, pipefail would cause the script to abort before we can react.
pushd "$WORKTREE" > /dev/null
set +e
run git merge origin/main -m "$MSG"
MERGE_EXIT=$?
set -e
popd > /dev/null
# -----------------------------------------------------------------------------
# OUTCOME: SUCCESS β push and clean up
# -----------------------------------------------------------------------------
if [[ $MERGE_EXIT -eq 0 ]]; then
info "Merge successful. Pushing $BRANCH to origin..."
pushd "$WORKTREE" > /dev/null
run git push -u origin "$BRANCH"
popd > /dev/null
info "Cleaning up worktree and local branch..."
run git worktree remove "$WORKTREE"
git branch -d "$BRANCH" 2>/dev/null \
|| git branch -D "$BRANCH" 2>/dev/null \
|| true
echo ""
echo "OK: merged main -> $BRANCH in '$MODULE' at $STAMP"
# -----------------------------------------------------------------------------
# OUTCOME: CONFLICT β show exactly what conflicts exist and guide resolution
# -----------------------------------------------------------------------------
else
# List the conflicting files immediately so the user knows where to look
echo ""
echo "!! Merge conflict detected. Conflicting files:"
git -C "$WORKTREE" diff --name-only --diff-filter=U \
| sed 's/^/ - /'
cat <<EOF
Resolve conflicts manually, then push:
Step 1 β Go to the worktree where the conflict lives:
cd $WORKTREE
Step 2 β See the full status:
git status
Step 3 β Open each conflicting file and resolve the markers:
<<<<<<< HEAD β your branch ($BRANCH)
your code
=======
incoming code
>>>>>>> origin/main β what came from main
Step 4 β Mark each resolved file as done:
git add <file>
Step 5 β Complete the merge commit:
git commit
Step 6 β Push the resolved branch:
git push -u origin $BRANCH
Step 7 β Clean up the worktree (run from the repo root, not inside the worktree):
cd -
git worktree remove $WORKTREE
git branch -D $BRANCH
TIP: To abandon the merge entirely and start over:
git merge --abort β run this inside $WORKTREE first
then re-run this script from the repo root.
EOF
exit 1
fi
π€ Contributing
Found a bug? Have a feature request? Contributions are welcome!
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
π License
This script is provided as-is under the MIT License. Feel free to use, modify, and distribute.
π Acknowledgments
Built with love for developers who are tired of:
- Switching branches constantly
- Merge conflicts destroying their flow
- Forgetting which branches need updates
- Manual, error-prone sync processes
Happy merging! π