Skip to main content
Dirty’s --raw flag and exit codes make it ideal for shell scripting and automation. This guide shows practical examples of using Dirty in scripts.

Machine-readable output with —raw

The --raw flag outputs one repository path per line with no decorations, making it easy to process in scripts.
# Normal output (human-friendly)
$ dirty ~/code
 * apps/dashboard
   apps/storefront
 * libs/ui-kit [local]

3 repos, 2 dirty, 1 local-only

# Raw output (script-friendly)
$ dirty --raw ~/code
apps/dashboard
apps/storefront
libs/ui-kit
The --raw flag suppresses colors, symbols, and summary statistics, outputting only repository paths relative to the scanned directory.

Exit codes

Dirty uses standard exit codes for script control flow:
  • Exit 0: Found repositories matching the criteria
  • Exit 1: No repositories found, or no repositories matching filters
if dirty --dirty ~/code > /dev/null 2>&1; then
    echo "Found dirty repos"
else
    echo "All repos are clean"
fi

Practical examples

Auto-commit all dirty repositories

Automatically commit changes in all dirty repos with a timestamp:
#!/bin/bash
set -e

BASE_DIR="$HOME/code"
COMMIT_MSG="Auto-commit: $(date +'%Y-%m-%d %H:%M:%S')"

# Find all dirty repos
dirty --dirty --raw "$BASE_DIR" | while IFS= read -r repo; do
    echo "Committing changes in $repo..."
    cd "$BASE_DIR/$repo"
    git add -A
    git commit -m "$COMMIT_MSG" || echo "Nothing to commit in $repo"
done

echo "Done!"
Be careful with auto-commit scripts. Always review what will be committed or use git add -p for interactive staging.

Pull all repositories with remotes

Update all repositories that have remotes configured:
#!/bin/bash
BASE_DIR="$HOME/code"

# Find all repos (excluding local-only ones)
dirty --raw "$BASE_DIR" > /tmp/all_repos.txt
dirty --local --raw "$BASE_DIR" > /tmp/local_repos.txt

# Get repos with remotes (set difference)
grep -vxFf /tmp/local_repos.txt /tmp/all_repos.txt | while IFS= read -r repo; do
    echo "Pulling $repo..."
    git -C "$BASE_DIR/$repo" pull --rebase || echo "Failed to pull $repo"
done

rm /tmp/all_repos.txt /tmp/local_repos.txt

Batch operations with xargs

Use xargs to run git commands across multiple repositories:
# Fetch all repos in parallel (4 at a time)
dirty --raw ~/code | xargs -I {} -P 4 git -C ~/code/{} fetch --all

# Check status of all dirty repos
dirty --dirty --raw ~/code | xargs -I {} sh -c 'echo "=== {} ===" && git -C ~/code/{} status -s'

# Add a git remote to all local-only repos
dirty --local --raw ~/code | xargs -I {} git -C ~/code/{} remote add origin https://github.com/user/{}.git
Use xargs -P to run operations in parallel. This is especially useful for slow operations like git fetch.

Using while read loops

Process each repository with custom logic:
#!/bin/bash
BASE_DIR="$HOME/code"

dirty --dirty --raw "$BASE_DIR" | while IFS= read -r repo; do
    full_path="$BASE_DIR/$repo"
    
    # Get commit count
    commits=$(git -C "$full_path" rev-list --count HEAD)
    
    # Get number of changed files
    changed=$(git -C "$full_path" status --porcelain | wc -l)
    
    echo "$repo: $changed files changed, $commits total commits"
done

Combining with other git commands

Check which dirty repos also have unpushed commits:
#!/bin/bash
BASE_DIR="$HOME/code"

dirty --dirty --raw "$BASE_DIR" | while IFS= read -r repo; do
    cd "$BASE_DIR/$repo"
    
    # Check if branch has upstream
    if git rev-parse --abbrev-ref @{upstream} >/dev/null 2>&1; then
        # Check if ahead of upstream
        ahead=$(git rev-list --count @{upstream}..HEAD)
        if [ "$ahead" -gt 0 ]; then
            echo "$repo: dirty + $ahead unpushed commits"
        else
            echo "$repo: dirty (no unpushed commits)"
        fi
    else
        echo "$repo: dirty (no upstream branch)"
    fi
done
Dirty has a built-in --include-unpushed flag that shows unpushed commits, but this example demonstrates combining Dirty with custom git commands.

Create a backup script

Archive all dirty repositories before cleanup:
#!/bin/bash
set -e

BASE_DIR="$HOME/code"
BACKUP_DIR="$HOME/backups/repos-$(date +%Y%m%d)"

if ! dirty --dirty --raw "$BASE_DIR" > /dev/null 2>&1; then
    echo "No dirty repos found. Nothing to backup."
    exit 0
fi

mkdir -p "$BACKUP_DIR"

dirty --dirty --raw "$BASE_DIR" | while IFS= read -r repo; do
    repo_name=$(basename "$repo")
    echo "Backing up $repo..."
    tar -czf "$BACKUP_DIR/${repo_name}.tar.gz" -C "$BASE_DIR" "$repo"
done

echo "Backup complete: $BACKUP_DIR"

Generate a report

Create a markdown report of repository status:
#!/bin/bash
BASE_DIR="$HOME/code"
REPORT="repo-status-$(date +%Y%m%d).md"

cat > "$REPORT" << EOF
# Repository Status Report
Generated: $(date)

## Dirty Repositories
EOF

if dirty --dirty --raw "$BASE_DIR" > /dev/null 2>&1; then
    dirty --dirty --raw "$BASE_DIR" | while IFS= read -r repo; do
        echo "- \`$repo\`" >> "$REPORT"
    done
else
    echo "None" >> "$REPORT"
fi

cat >> "$REPORT" << EOF

## Local-Only Repositories
EOF

if dirty --local --raw "$BASE_DIR" > /dev/null 2>&1; then
    dirty --local --raw "$BASE_DIR" | while IFS= read -r repo; do
        echo "- \`$repo\`" >> "$REPORT"
    done
else
    echo "None" >> "$REPORT"
fi

echo "Report saved to $REPORT"

Best practices

1
Always use —raw in scripts
2
The --raw flag ensures consistent output that won’t break if Dirty’s display format changes.
3
Handle errors gracefully
4
Use || true or check exit codes to prevent scripts from stopping on individual failures:
5
dirty --raw ~/code | while read -r repo; do
    git -C ~/code/"$repo" pull || echo "Failed: $repo"
done
6
Quote variables
7
Always quote variables to handle paths with spaces:
8
# Good
git -C "$BASE_DIR/$repo" status

# Bad (breaks on spaces)
git -C $BASE_DIR/$repo status
9
Test with edge cases
10
Test your scripts with:
11
  • Repositories with no commits
  • Detached HEAD state
  • Repositories with spaces in names
  • Empty results from Dirty
  • Build docs developers (and LLMs) love