Skip to main content

Modern git worktree manager with color coding and automation

Project description

⛩️ Portal - Git Worktree Manager

Python 3.12+ Code style: ruff Type Checked: mypy License: MIT

Portal Demo

Portal is a modern CLI tool for managing Git worktrees with automatic color coding and editor integration. Each worktree gets a deterministic color that syncs across your terminal, IDE, and CLI output, making it easy to identify which worktree you're working in.

Features

Color Coding System

  • Deterministic colors: Each worktree gets a consistent color based on its name
  • Cross-tool sync: Colors appear in iTerm tabs, VS Code/Cursor themes, and CLI output
  • Visual identification: Quickly identify which worktree you're working in

Automation & Hooks

  • 8 hook types: command, copy, template, mkdir, symlink, env_update, script, git
  • Project-level overrides: Use .portal files for project-specific automation
  • Conditional execution: Run hooks based on file existence, platform, environment
  • Security-hardened: Built-in protection against injection attacks and path traversal
  • Variable substitution: Dynamic values like {{worktree_name}}, {{color_hex}}, auto ports

Developer Tools

  • Interactive menu: Arrow-key navigation with color-highlighted worktree entries
  • iTerm integration: Open worktrees in new tabs with colored tab indicators
  • Claude integration: Start AI-assisted coding sessions in worktree context
  • Shell completions: Support for Bash and Zsh

Technical Design

  • Type-safe: Fully type-checked with mypy strict mode
  • Async operations: Built with Python's async/await for better performance
  • Event-driven: Extensible architecture using event bus pattern
  • Cross-platform: Works on macOS and Linux (Windows via WSL)

Installation

Prerequisites

  • Python 3.12 or higher
  • Git 2.5+ (for worktree support)
  • iTerm2 (optional, for terminal integration on macOS)
  • VS Code or Cursor (optional, for editor integration)

Quick Install

# With uv (recommended)
uv tool install git-portal[cli]

# With pipx
pipx install git-portal[cli]

# With Homebrew (macOS)
brew tap aureliensibiril/portal
brew install portal

From Source

git clone https://github.com/aureliensibiril/portal.git
cd portal
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[cli]"

Shell Integration

Install shell completions and functions:

# Install shell completions and portal cd function
portal shell install

This adds tab completion for Portal commands and the pw alias for quick worktree switching.

Quick Start

Create Your First Worktree

# Interactive mode - shows numbered list with color indicators
portal list

# Create a new worktree
portal new feature/awesome-feature

# Create from specific base branch
portal new hotfix/urgent-fix --base release-v2.0

Example Workflow

# 1. Open the interactive menu (arrow-key navigation with true-color entries)
$ portal

⛩️ Portal - Git Worktree Manager
↑/↓ Navigate  Enter: Select  N: New worktree  Q: Quit

▶ main (base)              # highlighted, colored per worktree
  feature/auth
  hotfix/bug

# Pressing Enter on a worktree opens an action submenu:
# ↵  Open terminal in new tab
# 📝 Open in Cursor
# 🤖 Open with Claude
# 🎨 Reset IDE colors
# 🗑️  Delete worktree

# 2. Create new worktree with hooks
$ portal new feature/payments
✅ Created worktree: feature/payments
   Path: ../portal_worktrees/feature_payments
   Color: Purple (#9C27B0) Hook: Copy environment file
✅ Hook: Install dependencies
✅ Opened in Cursor with Purple theme

# 3. Switch between worktrees
$ portal switch feature/auth
✅ Switched to feature/auth

# 4. Open in iTerm with tab color
$ portal terminal feature/payments
✅ Opened feature/payments in new iTerm tab

# 5. Start AI coding session
$ portal claude feature/payments
✅ Opened feature/payments with Claude

Command Reference

Core Worktree Management

Command Description Example
portal Open interactive menu portal
portal list Interactive worktree list with colors portal list --format table
portal new <branch> Create new worktree portal new feature/auth --base develop
portal delete [worktree] Delete a worktree portal delete feature/old --force
portal switch [worktree] Switch to a worktree portal switch main --open
portal info [worktree] Show worktree information portal info feature/auth
portal branches List available branches portal branches --fetch

Options detail:

Command Option Description
portal new -b/--base <branch> Base branch for the new worktree
portal new -t/--template <name> Template to use
portal new --fetch Fetch remote branches before creating
portal new --no-hooks Skip hook execution
portal new --no-open Don't open in editor after creation
portal delete -f/--force Force deletion
portal delete --with-branch Also delete the associated branch
portal list --format [interactive|simple|table|json] Output format (default: interactive)
portal list --no-interactive Disable interactive mode
portal switch --open Open in editor after switching

Editor Integration

Command Description Example
portal cursor [worktree] Open in Cursor IDE with color theme portal cursor feature/auth
portal vscode [worktree] Open in VS Code with color theme portal vscode main

Terminal Integration

Command Description Example
portal terminal [worktree] Open iTerm tab with color portal terminal feature/ui
portal claude [worktree] Open iTerm with Claude AI portal claude hotfix/bug

Hook Management

Command Description Example
portal hooks list Show hook configuration guidance portal hooks list
portal hooks run <stage> Run hooks for a specific stage portal hooks run post_create --dry-run

Configuration

Command Description Example
portal config show Display current configuration with sources portal config show
portal config show --json Show configuration as JSON portal config show --json
portal config show --global Show only global configuration portal config show --global
portal config edit Edit configuration file portal config edit --global
portal config set Set a configuration value portal config set editor.default vscode --global

Integration Management

Command Description Example
portal integrations list List available integrations portal integrations list
portal integrations status Show status of all integrations portal integrations status
portal integrations test Test all available integrations portal integrations test

Utility Commands

Command Description Example
portal shell install Install shell completions portal shell install
portal --version Show Portal version portal --version

Configuration

Portal uses YAML configuration files with sensible defaults. You can inspect the current configuration and see exactly what values Portal will use with the config show command.

Viewing Configuration

The portal config show command displays the merged configuration from all sources:

# Show current configuration with resolved paths and sources
portal config show

# Output as JSON for scripting
portal config show --json

# Show only global configuration
portal config show --global

This command shows:

  • Repository Context: Current Git repository, project name, and branch
  • Configuration Values: All settings with resolved paths showing exactly where worktrees will be created
  • Configuration Sources: Which config files are loaded (project .portal.yml, global ~/.portal/config.yml, and defaults)

Example Configuration (~/.portal/config.yml)

# Portal Configuration
version: "1.0"
base_dir: "../{project}_worktrees" # Where worktrees are created

# Color settings
colors:
  enabled: true
  sync_iterm: true # Sync colors to iTerm
  sync_cursor: true # Sync colors to Cursor
  sync_claude: true # Generate Claude context
  high_contrast: false # Use high-contrast palette

# Editor settings
editor:
  default: "cursor" # Default editor (cursor/vscode/vim)
  auto_open: true # Auto-open on creation

# Shell integration
shell:
  completions_enabled: true # Enable tab completion
  cd_function: true # Install 'portal cd' function
  prompt_integration: false # Show worktree in prompt

# Global hooks (can be overridden per-project)
hooks:
  post_create:
    - type: mkdir
      config:
        paths: ["logs", "tmp"]
    - type: command
      config:
        command: "echo 'Worktree created'"

# Branch pattern mappings
branch_patterns:
  "feature/*": "feature"
  "hotfix/*": "hotfix"
  "release/*": "release"
  "bugfix/*": "bugfix"

Hook System

Portal's powerful hook system automates worktree setup, teardown, and environment configuration. Hooks execute at specific lifecycle stages and support multiple operation types.

Hook Types

Portal supports 8 different hook types for comprehensive automation:

Hook Type Purpose Configuration
command Execute shell commands command: "npm install"
copy Copy files/directories from: ".env.example", to: ".env"
template Process Jinja2 templates template: "config.j2", output: "config.json"
mkdir Create directories paths: ["logs", "tmp", "cache"]
symlink Create symbolic links source: "../shared", target: "public"
env_update Update environment files file: ".env", updates: {...}
script Run custom scripts script: "setup.sh"
git Git operations operation: "fetch"

Hook Stages

Hooks execute at these lifecycle stages:

  • pre_create: Before worktree creation
  • post_create: After worktree creation
  • pre_delete: Before worktree deletion
  • post_delete: After worktree deletion
  • pre_switch: Before switching worktrees
  • post_switch: After switching worktrees
  • pre_list: Before listing worktrees
  • post_list: After listing worktrees

Note: The portal hooks run command supports manually running post_create, pre_delete, pre_switch, and post_switch. The other stages are triggered automatically by their respective operations.

Configuration Hierarchy

Portal merges configuration from multiple sources (in order of priority):

  1. .portal.yml - Project configuration (highest priority)
  2. ~/.portal/config.yml - Global configuration
  3. Default configuration (lowest priority)

Each level merges with the previous, allowing you to override specific values without redefining everything.

Basic Hook Configuration

Global Configuration (~/.portal/config.yml):

version: "1.0"
base_dir: "../{project}_worktrees"

hooks:
  post_create:
    - type: command
      config:
        command: "echo 'Setting up worktree...'"
    - type: mkdir
      config:
        paths: ["logs", "tmp"]

Project Configuration (.portal.yml in project root):

# These hooks merge with (and override) global hooks
hooks:
  post_create:
    # Copy files from project root to new worktree
    - type: copy
      config:
        from: ".env.local.example"
        to: ".env"
      name: "Setup local environment"

    # Install dependencies conditionally
    - type: command
      config:
        command: "npm install --frozen-lockfile"
      condition: "file_exists:package.json"
      name: "Install Node.js dependencies"

    # Auto-configure environment with dynamic values
    - type: env_update
      config:
        file: ".env"
        updates:
          DATABASE_NAME: "{{project}}_{{worktree_name}}_dev"
          API_PORT: "auto" # Automatically finds available port
          REDIS_PREFIX: "{{worktree_name}}:"
      name: "Configure development environment"

  pre_delete:
    # Cleanup before worktree deletion
    - type: command
      config:
        command: "docker-compose down -v"
      condition: "file_exists:docker-compose.yml"
      on_error: "warn"
      name: "Stop Docker services"

Advanced Hook Features

Variable Substitution

All hooks support {{variable}} substitution:

hooks:
  post_create:
    - type: env_update
      config:
        file: ".env"
        updates:
          DB_NAME: "{{project}}_{{worktree_name}}_dev"
          WORKTREE_PATH: "{{worktree_path}}"
          ASSIGNED_COLOR: "{{color_hex}}"

Available Variables:

  • {{project}} - Project name (derived from worktree parent directory)
  • {{worktree_name}} - Worktree name
  • {{worktree_path}} - Full worktree path
  • {{worktree}} - Alias for {{worktree_path}}
  • {{branch}} - Git branch name
  • {{color_hex}} - Assigned color hex code (e.g., #3F51B5)
  • {{home}} - User home directory
  • {{user}} - Current username

Conditional Execution

Execute hooks only when conditions are met:

hooks:
  post_create:
    - type: command
      config:
        command: "npm install"
      condition: "file_exists:package.json"

    - type: command
      config:
        command: "pip install -r requirements.txt"
      condition: "file_exists:requirements.txt"

    - type: command
      config:
        command: "brew services start postgresql"
      condition: "platform:darwin"

Available Conditions:

  • file_exists:filename - File exists in worktree
  • file_not_exists:filename - File doesn't exist
  • dir_exists:dirname - Directory exists
  • env:VAR_NAME - Environment variable is set
  • env:VAR_NAME=value - Environment variable equals value
  • platform:darwin - Platform check (darwin/linux/win32)
  • command_success:command - Command executes successfully

Negation: Prefix any condition with ! to negate it (e.g., !file_exists:package.json runs the hook only when package.json does not exist).

Error Handling

Control what happens when hooks fail:

hooks:
  post_create:
    - type: command
      config:
        command: "critical-setup"
      on_error: "fail" # Stop execution on failure

    - type: command
      config:
        command: "optional-setup"
      on_error: "warn" # Continue with warning (default)

    - type: command
      config:
        command: "nice-to-have"
      on_error: "ignore" # Continue silently

Common Hook Examples

Node.js Project Setup

# .portal.yml file
hooks:
  post_create:
    - type: copy
      config:
        from: ".env.example"
        to: ".env"

    - type: command
      config:
        command: "npm install"
      condition: "file_exists:package.json"

    - type: env_update
      config:
        file: ".env"
        updates:
          NODE_ENV: "development"
          PORT: "auto"

Rust Project Setup

# .portal.yml file
hooks:
  post_create:
    - type: env_update
      config:
        file: ".env"
        updates:
          RUST_LOG: "{{project}}={{worktree_name}}=debug"
          DATABASE_URL: "postgres://localhost/{{project}}_{{worktree_name}}"
      name: "Configure Rust environment"

    - type: command
      config:
        command: "cargo build --workspace"
      condition: "file_exists:Cargo.toml"
      name: "Build workspace"

    - type: command
      config:
        command: "cargo sqlx database setup"
      condition: "file_exists:migrations"
      on_error: "warn"
      name: "Setup database"

Full-Stack Development

# .portal.yml file
hooks:
  post_create:
    # Setup environment files
    - type: template
      config:
        template: "docker-compose.template.yml"
        output: "docker-compose.yml"
        variables:
          db_port: "{{color_hex}}"

    # Start services
    - type: command
      config:
        command: "docker-compose up -d postgres redis"

    # Install dependencies
    - type: command
      config:
        command: "npm run setup:dev"

  pre_delete:
    # Cleanup services
    - type: command
      config:
        command: "docker-compose down -v"
      on_error: "warn"

Security Features

Portal's hook system includes built-in security protections:

  • Command injection prevention: Dangerous shell operators blocked by default
  • Path traversal protection: Prevents ../ attacks in file operations
  • Input validation: All hook configurations validated
  • Secure defaults: Safe error handling and timeout protection

Shell Command Security

# ❌ This is blocked for security:
- type: command
  config:
    command: "echo test; rm -rf /"

# ✅ Explicit bypass when needed:
- type: command
  config:
    command: "echo test && echo done"
    allow_shell: true # Explicitly allow shell operators

Project-Level Configuration

Use .portal.yml file in your project root for project-specific configurations:

# Project structure
my-project/
├── .git/
├── .portal.yml      # Project configuration
└── src/

The .portal.yml file merges with and overrides global settings for project-specific automation.

Tips & Tricks

Productivity Aliases

After running portal shell install, you get:

  • pw <worktree> - Quick switch to a worktree and cd into it

Additional aliases you can add to your shell config:

# Quick worktree commands
alias pnew="portal new"
alias plist="portal list"
alias pdel="portal delete"

# Open in editor
alias pcursor="portal cursor"
alias pvscode="portal vscode"

Example Hook Scripts

Auto-install dependencies:

hooks:
  post_create:
    - type: command
      name: "Install deps"
      config:
        command: |
          if [ -f package.json ]; then npm install
          elif [ -f requirements.txt ]; then pip install -r requirements.txt
          elif [ -f Gemfile ]; then bundle install
          fi
        allow_shell: true

Rust project with workspace setup:

hooks:
  post_create:
    - type: command
      name: "Build workspace"
      config:
        command: "cargo build --workspace"
    - type: command
      name: "Setup git hooks"
      config:
        command: "cargo husky install"

Troubleshooting

Worktrees Created in Wrong Location

Use portal config show to verify where worktrees will be created:

# Check current configuration and resolved paths
portal config show

# Look for this section in the output:
#   Worktree Settings:
#     Base Directory Template: ../{project}_worktrees
#     Resolved Base Directory: ../myproject_worktrees
#     Actual Worktree Path: /Users/you/code/myproject_worktrees

If the path is incorrect, check your .portal.yml file:

# Default: Creates worktrees as siblings to main repo (recommended)
base_dir: "../{project}_worktrees"

# This would create worktrees inside the main repo (not recommended)
base_dir: "{project}_worktrees"

# This would create worktrees two levels up from the main repo
base_dir: "../../{project}_worktrees"

The base_dir path is relative to your main repository. Using ../ creates a sibling folder, which keeps worktrees organized and separate from your main codebase.

Configuration Not Loading

Verify which configuration files are being loaded:

portal config show
# Check the "Configuration Sources" section to see which files are found

Debugging Configuration Issues

# View merged configuration as JSON for detailed inspection
portal config show --json | jq '.'

# Check if running from correct Git repository
git rev-parse --show-toplevel

License

MIT License - see LICENSE file for details.


⛩️ Portal - Modern Git Worktree Manager

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

git_portal-0.1.1.tar.gz (88.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

git_portal-0.1.1-py3-none-any.whl (121.1 kB view details)

Uploaded Python 3

File details

Details for the file git_portal-0.1.1.tar.gz.

File metadata

  • Download URL: git_portal-0.1.1.tar.gz
  • Upload date:
  • Size: 88.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for git_portal-0.1.1.tar.gz
Algorithm Hash digest
SHA256 020cd42e86965ccf852fbc27abaa1cde3b3b781f79d5b5caadc7fbefb9caf4b8
MD5 640026e93fc7d67df8a28d568773da5e
BLAKE2b-256 9ec8c114bc277ffe21db3b8423368d1ec9019fcf459b600fcdd9dab851c8c64d

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_portal-0.1.1.tar.gz:

Publisher: publish.yml on aureliensibiril/portal

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file git_portal-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: git_portal-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 121.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for git_portal-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ee02ba9cd5b2b8bc147e9b6a40782b2edf707c07c81a391bf611679cec083cce
MD5 9f2518ebd5711611e028246b6dbcc2eb
BLAKE2b-256 b6f7db6b3f4ebcfaf187bb0b46f682fa04974cd8168ec6e5449c22af86f501bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_portal-0.1.1-py3-none-any.whl:

Publisher: publish.yml on aureliensibiril/portal

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page