Git Worktree を楽に扱うためのスクリプトと Copilot の活用例
最終的にどうなったか
最終的には、以下のような形で Git Worktree を使っています。
-
プロンプトの実行

Issue 番号を入力するだけで実行できます -
実行内容の提案と確認

Issue 内容をもとに、スクリプトへ渡すパラメータを提案し、実行前に確認します -
必要に応じてその場で修正

提案内容に調整が必要な場合は、チャット内のターミナル上で修正できます -
問題なければ承認して実行


承認後、新しいワークスペースが起動します。ウィンドウタイトルが設定されるため、作業内容を把握しやすくなっています
- GitHub Copilot の カスタムプロンプトに Issue 番号を渡すだけ
- 新しい Worktree が作成され
- 新規ブランチの作成・チェックアウト
- VSCode の新しいワークスペース起動
- ウィンドウタイトル・色の設定
- 必要なファイルのコピー
までを 一連の流れで自動化しています。
コマンドを覚える必要はなく、
日常的な操作は Copilot のチャットから完結します。
はじめに
Git Worktree は、最近よく見かけるようになった Git の機能です。
AI と並列で開発するケースが増えたことで、
複数ブランチを同時に扱える点が評価されているのだと思います。
一方で、個人的には次の点がずっと気になっていました。
- 結局は Git Worktree のコマンドを覚える必要がある
- 導入時の学習コストがやや高い
- 「業務改善」と言うには、少し手間が多い
便利なのは分かるものの、
日常業務に定着させるのが難しいと感じていました。
Git Worktree をざっくり説明すると
Git Worktree は、
- 1つの Git リポジトリをルートとして
- 別ディレクトリに追加の作業環境を作成できる仕組み
です。
ブランチごとにディレクトリが分かれるため、
- 実装中の作業を止めずにレビュー対応
- 別チケットの対応を並行して進める
といった使い方が可能になります。
VSCode では GUI から Worktree を確認でき、
新しいウィンドウとして開くこともできます。
個人的に感じているメリット
一番大きいと感じているのは、
今やっている作業を中断しなくていいこと
です。
- stash やブランチ切り替えが不要
- コンテキストスイッチが減る
- 複数のタスクを自然に並行できる
結果として、
作業の流れがかなりスムーズになりました。
ただし、コマンド前提だと定着しづらい
多くの記事では Git Worktree の便利さが紹介されていますが、
読んでいて感じたのは、
コマンドを覚える前提だと、業務改善としては少し厳しい
という点でした。
最近は CLI を直接叩く場面も減っていますし、
たまにしか使わないコマンドはすぐ忘れてしまいます。
そこで、
自分の業務に特化したスクリプトを作る
という方向に舵を切りました。
作成した Git Worktree スクリプトでやっていること
内部的には Git Worktree を使っていますが、
利用者側はそれを意識しなくて済むようにしています。
主に以下の処理をまとめています。
- Worktree の作成
- ブランチの作成・チェックアウト
- VSCode の新規ワークスペース起動
- ウィンドウタイトル・色の設定
- 必要なファイルのコピー
正直、今見ると
「少しやりすぎているな」と感じる部分もあります。
スクリプトの全体像(クリックで展開)
#!/bin/bash
# =====================================================================
# Git Worktree作成スクリプト - 使いかたガイド
#
# 概要:
# - 指定したベースブランチから Git Worktree を作成し、必要に応じて新規ブランチを派生します。
# - VS Code を新しいウィンドウで開き、ワークスペースの色設定を自動/手動で適用します。
# - 入力が `origin/<branch>` 形式でも自動的に正規化して動作します。
#
# 引数:
# 1) ベースブランチ名 例: main, base/feature-x
# 2) worktree名 例: feature/new-work, issue/45-new-feature
# 3) 新規ブランチ作成フラグ y|n(省略可、デフォルト n)
# 4) 色(省略可) blue|green|purple|cyan|orange(未指定時は自動判定)
#
# 自動色選択ルール:
# - 名前が review-* → purple(レビュー)
# - 名前が test-* → cyan(テスト)
# - 名前が experiment-* → orange(実験)
# - 上記以外 → green(開発)
#
# 代表的なユースケース:
# 1) リモートのベースから派生ブランチを作成
# bash ./scripts/create-worktree.sh origin/base/feature-x issue/45-new-feature y green
# → ベースが別worktreeで使用中でも、新規ブランチ issue/45-new-feature を派生して作成。
#
# 2) 既存ベースブランチでそのまま作業
# bash ./scripts/create-worktree.sh main review-feature/ui n purple
# → main が未使用なら main を checkout。使用中なら自動で新規ブランチ(worktree名由来)を作成。
#
# 3) 色の自動判定を使う
# bash ./scripts/create-worktree.sh base/feature-y experiment-data-reader y
# → worktree名が experiment-* なので orange が適用。色は .vscode/settings.json に反映。
#
# 後片付け(削除):
# - Worktree の削除: git worktree remove <作成先パス>
# - ブランチも削除: git branch -D <作成したブランチ名>
# =====================================================================
# Git Worktree作成スクリプト
# 引数:
# $1 - ベースとなるブランチ名
# $2 - 作成するworktree名
# $3 - (オプション) 新規ブランチ作成フラグ (y/n)
# $4 - (オプション) ワークスペースの色 (blue|green|purple|cyan|orange または空文字で自動選択)
# $5 - (オプション) ウィンドウタイトル (例: "Issue #45 - 新機能実装")
set -e
# スクリプトのディレクトリを取得
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 色付きメッセージ用の設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 使用方法の表示
usage() {
echo "使用方法: $0 <ベースブランチ名> <worktree名> [y/n] [色] [ウィンドウタイトル]"
echo ""
echo "引数:"
echo " ベースブランチ名: ベースとなるブランチ (例: main, develop)"
echo " worktree名: 作成するworktree名 (例: feature/new-work)"
echo " 新規ブランチ (オプション): y/n (デフォルト: n)"
echo " 色 (オプション): ワークスペースの色"
echo " - blue: ブルー (メイン)"
echo " - green: グリーン (開発)"
echo " - purple: パープル (レビュー)"
echo " - cyan: シアン (テスト)"
echo " - orange: オレンジ (実験)"
echo " - 空: 自動選択 (デフォルト)"
echo " ウィンドウタイトル (オプション): VS Codeのウィンドウタイトル"
echo " 例: 'Issue #45 - 新機能実装'"
echo ""
echo "例:"
echo " $0 main feature/new-work"
echo " $0 main feature/new-work y"
echo " $0 main review-feature/ui n purple"
echo " $0 main issue/45-new-feature y green 'Issue #45 - 新機能実装'"
echo ""
echo "このスクリプトは指定されたベースブランチから新しいworktreeを作成し、"
echo "そのディレクトリでVS Codeを開きます。"
echo ""
echo "自動色選択ルール:"
echo " - review- で始まる → purple (レビュー)"
echo " - test- で始まる → cyan (テスト)"
echo " - experiment- で始まる → orange (実験)"
echo " - その他 → green (開発)"
exit 1
}
# Worktree名から自動的に色を選択する関数
select_workspace_color() {
local worktree_name="$1"
local manual_color="$2"
# 手動指定がある場合はそれを優先
if [ -n "$manual_color" ]; then
# 有効な色かチェック
case "$manual_color" in
blue|green|purple|cyan|orange)
echo "$manual_color"
return
;;
*)
echo -e "${RED}エラー: 無効な色指定です: ${manual_color}${NC}" >&2
echo -e "${YELLOW}有効な色: blue, green, purple, cyan, orange${NC}" >&2
exit 1
;;
esac
fi
# Worktree名から自動判定
if [[ "$worktree_name" == review-* ]]; then
echo "purple"
elif [[ "$worktree_name" == test-* ]]; then
echo "cyan"
elif [[ "$worktree_name" == experiment-* ]]; then
echo "orange"
else
echo "green"
fi
}
# 引数チェック
if [ $# -lt 2 ]; then
echo -e "${RED}エラー: 引数が不足しています${NC}"
usage
fi
BASE_BRANCH="$1"
WORKTREE_NAME="$2"
CREATE_NEW_INPUT="${3:-n}" # 第3引数(新規ブランチ作成)、デフォルトはn
WORKSPACE_COLOR="${4:-}" # 第4引数(色指定)、デフォルトは空文字
WINDOW_TITLE="${5:-}" # 第5引数(ウィンドウタイトル)、デフォルトは空文字
# 第3引数がyの場合、新規ブランチ作成フラグをtrueに
CREATE_NEW_BRANCH_FLAG=false
if [[ "$CREATE_NEW_INPUT" == "y" || "$CREATE_NEW_INPUT" == "Y" ]]; then
CREATE_NEW_BRANCH_FLAG=true
fi
# ワークスペースの色を決定
SELECTED_COLOR=$(select_workspace_color "$WORKTREE_NAME" "$WORKSPACE_COLOR")
# 入力されたベースブランチの正規化(origin/付きも許容)
ORIGINAL_BASE_BRANCH="$BASE_BRANCH"
REMOTE_NAME="origin"
if [[ "$BASE_BRANCH" == ${REMOTE_NAME}/* ]]; then
NORMALIZED_LOCAL_BRANCH="${BASE_BRANCH#${REMOTE_NAME}/}"
NORMALIZED_REMOTE_BRANCH="${REMOTE_NAME}/${NORMALIZED_LOCAL_BRANCH}"
else
NORMALIZED_LOCAL_BRANCH="$BASE_BRANCH"
NORMALIZED_REMOTE_BRANCH="${REMOTE_NAME}/${BASE_BRANCH}"
fi
# YAMLファイルから色設定を読み込む関数
read_color_config() {
local color="$1"
local config_file="${SCRIPT_DIR}/workspace-colors.yaml"
local key="$2"
# YAMLから指定されたキーの値を取得(簡易パーサー)
local section_found=false
local result=""
while IFS= read -r line; do
# 色セクションの開始を検出
if [[ "$line" =~ ^[[:space:]]*${color}:[[:space:]]*$ ]]; then
section_found=true
continue
fi
# 次の色セクションに到達したら終了
if $section_found && [[ "$line" =~ ^[[:space:]]*[a-z]+:[[:space:]]*$ ]] && [[ ! "$line" =~ ^[[:space:]]{4,} ]]; then
break
fi
# 指定されたキーを検索
if $section_found && [[ "$line" =~ ${key}:[[:space:]]*\"(.+)\" ]]; then
result="${BASH_REMATCH[1]}"
break
fi
done < "$config_file"
echo "$result"
}
# .vscode/settings.jsonファイルを生成する関数
generate_workspace_settings() {
local worktree_path="$1"
local color="$2"
local vscode_dir="${worktree_path}/.vscode"
local settings_file="${vscode_dir}/settings.json"
echo -e "${YELLOW}ワークスペース設定ファイルを作成中...${NC}"
# .vscodeディレクトリを作成
mkdir -p "$vscode_dir"
# メインリポジトリの.vscode/settings.jsonをコピー(存在する場合)
local main_settings="${REPO_ROOT}/.vscode/settings.json"
if [ -f "$main_settings" ]; then
echo -e "${YELLOW}メインリポジトリの設定をコピーしています...${NC}"
cp "$main_settings" "$settings_file"
fi
# 色設定を読み込み
local peacock_color=$(read_color_config "$color" "peacock")
local title_bg=$(read_color_config "$color" "activeBackground")
local title_fg=$(read_color_config "$color" "activeForeground")
local title_inactive_bg=$(read_color_config "$color" "inactiveBackground")
local title_inactive_fg=$(read_color_config "$color" "inactiveForeground")
local status_bg=$(read_color_config "$color" "background")
local status_fg=$(read_color_config "$color" "foreground")
local activity_bg=""
local activity_fg=""
local activity_inactive_fg=""
local badge_bg=""
local badge_fg=""
# activityBarとactivityBarBadgeの設定を個別に読み込み
local in_activity_bar=false
local in_activity_badge=false
local in_color_section=false
while IFS= read -r line; do
# 色セクションの検出
if [[ "$line" =~ ^[[:space:]]*${color}:[[:space:]]*$ ]]; then
in_color_section=true
continue
fi
# 他の色セクションに入ったら終了
if $in_color_section && [[ "$line" =~ ^[[:space:]]*[a-z]+:[[:space:]]*$ ]] && [[ ! "$line" =~ ^[[:space:]]{2,} ]]; then
break
fi
if $in_color_section; then
# activityBar セクション
if [[ "$line" =~ ^[[:space:]]{2}activityBar:[[:space:]]*$ ]]; then
in_activity_bar=true
in_activity_badge=false
continue
fi
# activityBarBadge セクション
if [[ "$line" =~ ^[[:space:]]{2}activityBarBadge:[[:space:]]*$ ]]; then
in_activity_badge=true
in_activity_bar=false
continue
fi
# statusBar や titleBar セクションに入ったら activityBar を抜ける
if [[ "$line" =~ ^[[:space:]]{2}(statusBar|titleBar):[[:space:]]*$ ]]; then
in_activity_bar=false
in_activity_badge=false
continue
fi
# activityBar 内の設定を読み込み
if $in_activity_bar; then
if [[ "$line" =~ background:[[:space:]]*\"(.+)\" ]]; then
activity_bg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ foreground:[[:space:]]*\"(.+)\" ]] && [[ ! "$line" =~ inactiveForeground ]]; then
activity_fg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ inactiveForeground:[[:space:]]*\"(.+)\" ]]; then
activity_inactive_fg="${BASH_REMATCH[1]}"
fi
fi
# activityBarBadge 内の設定を読み込み
if $in_activity_badge; then
if [[ "$line" =~ background:[[:space:]]*\"(.+)\" ]]; then
badge_bg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ foreground:[[:space:]]*\"(.+)\" ]]; then
badge_fg="${BASH_REMATCH[1]}"
fi
fi
fi
done < "${SCRIPT_DIR}/workspace-colors.yaml"
# 色設定のJSON文字列を作成
local color_settings=$(cat <<EOF
"workbench.colorCustomizations": {
"titleBar.activeBackground": "${title_bg}",
"titleBar.activeForeground": "${title_fg}",
"titleBar.inactiveBackground": "${title_inactive_bg}",
"titleBar.inactiveForeground": "${title_inactive_fg}",
"statusBar.background": "${status_bg}",
"statusBar.foreground": "${status_fg}",
"activityBar.background": "${activity_bg}",
"activityBar.foreground": "${activity_fg}",
"activityBar.inactiveForeground": "${activity_inactive_fg}",
"activityBarBadge.background": "${badge_bg}",
"activityBarBadge.foreground": "${badge_fg}"
},
"peacock.color": "${peacock_color}"
EOF
)
# 既存のsettings.jsonがある場合はマージ、ない場合は新規作成
if [ -f "$settings_file" ]; then
# 既存ファイルから workbench.colorCustomizations と peacock.color を削除してから追加
# jqがインストールされているか確認
if command -v jq >/dev/null 2>&1; then
# jqを使用してマージ
local temp_file="${settings_file}.tmp"
jq --argjson colors "{\"workbench.colorCustomizations\": {
\"titleBar.activeBackground\": \"${title_bg}\",
\"titleBar.activeForeground\": \"${title_fg}\",
\"titleBar.inactiveBackground\": \"${title_inactive_bg}\",
\"titleBar.inactiveForeground\": \"${title_inactive_fg}\",
\"statusBar.background\": \"${status_bg}\",
\"statusBar.foreground\": \"${status_fg}\",
\"activityBar.background\": \"${activity_bg}\",
\"activityBar.foreground\": \"${activity_fg}\",
\"activityBar.inactiveForeground\": \"${activity_inactive_fg}\",
\"activityBarBadge.background\": \"${badge_bg}\",
\"activityBarBadge.foreground\": \"${badge_fg}\"
}, \"peacock.color\": \"${peacock_color}\"}" \
'. + $colors' "$settings_file" > "$temp_file" && mv "$temp_file" "$settings_file"
elif command -v python >/dev/null 2>&1; then
# Pythonを使用してマージ(JSONC対応:コメントを削除)
python <<PYTHON_SCRIPT
import json
import re
import sys
def remove_jsonc_comments(text):
"""JSONCからコメントを削除"""
# 単一行コメント (//) を削除
text = re.sub(r'//.*$', '', text, flags=re.MULTILINE)
# 複数行コメント (/* */) を削除
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
return text
try:
with open('${settings_file}', 'r', encoding='utf-8') as f:
content = f.read()
# コメントを削除してからパース
clean_json = remove_jsonc_comments(content)
settings = json.loads(clean_json)
settings['workbench.colorCustomizations'] = {
'titleBar.activeBackground': '${title_bg}',
'titleBar.activeForeground': '${title_fg}',
'titleBar.inactiveBackground': '${title_inactive_bg}',
'titleBar.inactiveForeground': '${title_inactive_fg}',
'statusBar.background': '${status_bg}',
'statusBar.foreground': '${status_fg}',
'activityBar.background': '${activity_bg}',
'activityBar.foreground': '${activity_fg}',
'activityBar.inactiveForeground': '${activity_inactive_fg}',
'activityBarBadge.background': '${badge_bg}',
'activityBarBadge.foreground': '${badge_fg}'
}
settings['peacock.color'] = '${peacock_color}'
with open('${settings_file}', 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2, ensure_ascii=False)
sys.exit(0)
except Exception as e:
print("Error: " + str(e), file=sys.stderr)
sys.exit(1)
PYTHON_SCRIPT
else
echo -e "${YELLOW}警告: jqまたはPythonが見つかりません。色設定のみのファイルを作成します。${NC}"
# jqもPythonもない場合は、色設定だけの新しいファイルを作成
cat > "$settings_file" <<EOF
{
${color_settings}
}
EOF
fi
else
# 新規作成
cat > "$settings_file" <<EOF
{
${color_settings}
}
EOF
fi
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ ワークスペース設定ファイルを作成しました${NC}"
echo " 色: ${color}"
else
echo -e "${RED}エラー: ワークスペース設定ファイルの作成に失敗しました${NC}"
fi
}
# .code-workspaceファイルを生成する関数
generate_code_workspace() {
local worktree_path="$1"
local color="$2"
local window_title="$3"
local workspace_file="${worktree_path}/workspace.code-workspace"
echo -e "${YELLOW}.code-workspaceファイルを作成中...${NC}"
# 色設定を読み込み
local peacock_color=$(read_color_config "$color" "peacock")
local title_bg=$(read_color_config "$color" "activeBackground")
local title_fg=$(read_color_config "$color" "activeForeground")
local title_inactive_bg=$(read_color_config "$color" "inactiveBackground")
local title_inactive_fg=$(read_color_config "$color" "inactiveForeground")
local status_bg=$(read_color_config "$color" "background")
local status_fg=$(read_color_config "$color" "foreground")
local activity_bg=""
local activity_fg=""
local activity_inactive_fg=""
local badge_bg=""
local badge_fg=""
# activityBarとactivityBarBadgeの設定を個別に読み込み
local in_activity_bar=false
local in_activity_badge=false
local in_color_section=false
while IFS= read -r line; do
# 色セクションの検出
if [[ "$line" =~ ^[[:space:]]*${color}:[[:space:]]*$ ]]; then
in_color_section=true
continue
fi
# 他の色セクションに入ったら終了
if $in_color_section && [[ "$line" =~ ^[[:space:]]*[a-z]+:[[:space:]]*$ ]] && [[ ! "$line" =~ ^[[:space:]]{2,} ]]; then
break
fi
if $in_color_section; then
# activityBar セクション
if [[ "$line" =~ ^[[:space:]]{2}activityBar:[[:space:]]*$ ]]; then
in_activity_bar=true
in_activity_badge=false
continue
fi
# activityBarBadge セクション
if [[ "$line" =~ ^[[:space:]]{2}activityBarBadge:[[:space:]]*$ ]]; then
in_activity_badge=true
in_activity_bar=false
continue
fi
# statusBar や titleBar セクションに入ったら activityBar を抜ける
if [[ "$line" =~ ^[[:space:]]{2}(statusBar|titleBar):[[:space:]]*$ ]]; then
in_activity_bar=false
in_activity_badge=false
continue
fi
# activityBar 内の設定を読み込み
if $in_activity_bar; then
if [[ "$line" =~ background:[[:space:]]*\"(.+)\" ]]; then
activity_bg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ foreground:[[:space:]]*\"(.+)\" ]] && [[ ! "$line" =~ inactiveForeground ]]; then
activity_fg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ inactiveForeground:[[:space:]]*\"(.+)\" ]]; then
activity_inactive_fg="${BASH_REMATCH[1]}"
fi
fi
# activityBarBadge 内の設定を読み込み
if $in_activity_badge; then
if [[ "$line" =~ background:[[:space:]]*\"(.+)\" ]]; then
badge_bg="${BASH_REMATCH[1]}"
elif [[ "$line" =~ foreground:[[:space:]]*\"(.+)\" ]]; then
badge_fg="${BASH_REMATCH[1]}"
fi
fi
fi
done < "${SCRIPT_DIR}/workspace-colors.yaml"
# .code-workspaceファイルを作成
cat > "$workspace_file" <<EOF
{
"folders": [
{
"path": "."
}
],
"settings": {
"window.title": "${window_title}",
"workbench.colorCustomizations": {
"titleBar.activeBackground": "${title_bg}",
"titleBar.activeForeground": "${title_fg}",
"titleBar.inactiveBackground": "${title_inactive_bg}",
"titleBar.inactiveForeground": "${title_inactive_fg}",
"statusBar.background": "${status_bg}",
"statusBar.foreground": "${status_fg}",
"activityBar.background": "${activity_bg}",
"activityBar.foreground": "${activity_fg}",
"activityBar.inactiveForeground": "${activity_inactive_fg}",
"activityBarBadge.background": "${badge_bg}",
"activityBarBadge.foreground": "${badge_fg}"
},
"peacock.color": "${peacock_color}"
}
}
EOF
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ .code-workspaceファイルを作成しました${NC}"
echo " タイトル: ${window_title}"
else
echo -e "${RED}エラー: .code-workspaceファイルの作成に失敗しました${NC}"
fi
}
# worktree名の検証(ディレクトリトラバーサル攻撃の防止)
# 1. '..' や絶対パスのチェック
if [[ "$WORKTREE_NAME" == *".."* ]] || [[ "$WORKTREE_NAME" == /* ]]; then
echo -e "${RED}エラー: Worktree名に '..' や絶対パスは使用できません${NC}"
echo "例: feature/new-work, bugfix/issue-123"
exit 1
fi
# 2. 空の名前チェック
if [[ -z "$WORKTREE_NAME" ]]; then
echo -e "${RED}エラー: Worktree名を指定してください${NC}"
exit 1
fi
# 3. basenameでディレクトリトラバーサルを防止
# スラッシュを含む名前(例: feature/new-work)は許可するが、
# basename変換で変わってしまう不正なパスは拒否
# 例: "dir/../../../sensitive" は basename で "sensitive" になるので検出できる
if [[ "$WORKTREE_NAME" == *"/"* ]]; then
# スラッシュを含む場合、最後のコンポーネントだけbasenameで検証
LAST_COMPONENT=$(basename "$WORKTREE_NAME")
# 最後のコンポーネントに .. や . が含まれていないか確認
if [[ "$LAST_COMPONENT" == *".."* ]] || [[ "$LAST_COMPONENT" == "." ]]; then
echo -e "${RED}エラー: 無効なWorktree名です${NC}"
echo "例: feature/new-work, bugfix/issue-123"
exit 1
fi
else
# スラッシュを含まない場合、basename変換で変わらないか確認
SANITIZED_NAME=$(basename "$WORKTREE_NAME")
if [[ "$WORKTREE_NAME" != "$SANITIZED_NAME" ]]; then
echo -e "${RED}エラー: 無効なWorktree名です${NC}"
echo "例: feature/new-work, bugfix/issue-123"
exit 1
fi
fi
# リポジトリのルートディレクトリを取得
if ! REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then
echo -e "${RED}エラー: Gitリポジトリではありません${NC}"
exit 1
fi
# worktreeの作成先ディレクトリ(リポジトリの親ディレクトリに作成)
# スラッシュをハイフンに変換してディレクトリ名として使用(階層を作らない)
WORKTREE_DIR_NAME="${WORKTREE_NAME//\//-}"
WORKTREE_PATH="${REPO_ROOT}/../${WORKTREE_DIR_NAME}"
echo -e "${YELLOW}=== Git Worktree作成スクリプト ===${NC}"
echo ""
echo "ベースブランチ: ${BASE_BRANCH}"
echo "Worktree名: ${WORKTREE_NAME}"
echo "ディレクトリ名: ${WORKTREE_DIR_NAME}"
echo "作成先: ${WORKTREE_PATH}"
echo ""
# ベースブランチの存在確認
echo -e "${YELLOW}ベースブランチの確認中...${NC}"
if ! git show-ref --verify --quiet "refs/heads/${NORMALIZED_LOCAL_BRANCH}" && \
! git show-ref --verify --quiet "refs/remotes/${NORMALIZED_REMOTE_BRANCH}"; then
echo -e "${RED}エラー: ブランチ '${ORIGINAL_BASE_BRANCH}' が見つかりません${NC}"
echo "利用可能なブランチ:"
git branch -a
exit 1
fi
# ローカルブランチが存在しない場合、リモートから取得(正規化後の名称を使用)
if ! git show-ref --verify --quiet "refs/heads/${NORMALIZED_LOCAL_BRANCH}"; then
if git show-ref --verify --quiet "refs/remotes/${NORMALIZED_REMOTE_BRANCH}"; then
echo -e "${YELLOW}リモートブランチからローカルブランチを作成します...${NC}"
git branch "${NORMALIZED_LOCAL_BRANCH}" "${NORMALIZED_REMOTE_BRANCH}"
fi
fi
# -bフラグが指定された場合の処理
if [ "${CREATE_NEW_BRANCH_FLAG}" = true ]; then
echo -e "${YELLOW}新規ブランチを作成します...${NC}"
# 新しいブランチ名はworktree名をそのまま使用
NEW_BRANCH="${WORKTREE_NAME}"
# 新しいブランチが既に存在するかチェック
if git show-ref --verify --quiet "refs/heads/${NEW_BRANCH}"; then
echo -e "${RED}エラー: ブランチ '${NEW_BRANCH}' は既に存在します${NC}"
echo "別のworktree名を使用するか、既存のブランチを削除してください:"
echo " git branch -D ${NEW_BRANCH}"
exit 1
fi
# worktreeディレクトリ名はスラッシュをハイフンに変換
WORKTREE_DIR_NAME_FOR_NEW="${WORKTREE_NAME//\//-}"
WORKTREE_PATH="${REPO_ROOT}/../${WORKTREE_DIR_NAME_FOR_NEW}"
BRANCH_TO_USE="${NEW_BRANCH}"
CREATE_NEW_BRANCH=true
else
# -bフラグなしの場合、既存ブランチをチェックアウト
# ベースブランチが既に別のworktreeでチェックアウトされているかチェック
echo -e "${YELLOW}既存のworktreeを確認中...${NC}"
# より確実な方法: git worktree listの通常出力を使用
# 形式: "path commit [branch]" の3列目にブランチ名がある
EXISTING_WORKTREE=$(git worktree list | grep "\[${NORMALIZED_LOCAL_BRANCH}\]" | awk '{print $1}')
if [ -n "${EXISTING_WORKTREE}" ]; then
echo -e "${YELLOW}警告: ブランチ '${BASE_BRANCH}' は既に以下の場所でチェックアウトされています:${NC}"
echo " ${EXISTING_WORKTREE}"
echo ""
echo -e "${YELLOW}新しいブランチを作成してworktreeを作成します。${NC}"
# 新しいブランチ名を生成(worktreeディレクトリ名と同じにする)
NEW_BRANCH="${WORKTREE_DIR_NAME}"
# 新しいブランチが既に存在するかチェック
if git show-ref --verify --quiet "refs/heads/${NEW_BRANCH}"; then
echo -e "${RED}エラー: ブランチ '${NEW_BRANCH}' は既に存在します${NC}"
echo "別のworktree名を使用するか、既存のブランチを削除してください:"
echo " git branch -D ${NEW_BRANCH}"
exit 1
fi
BRANCH_TO_USE="${NEW_BRANCH}"
CREATE_NEW_BRANCH=true
else
BRANCH_TO_USE="${NORMALIZED_LOCAL_BRANCH}"
CREATE_NEW_BRANCH=false
fi
fi
# worktreeが既に存在するかチェック
if [ -d "${WORKTREE_PATH}" ]; then
echo -e "${RED}エラー: ディレクトリ '${WORKTREE_PATH}' は既に存在します${NC}"
exit 1
fi
# worktreeを作成
echo -e "${YELLOW}Worktreeを作成中...${NC}"
if [ "${CREATE_NEW_BRANCH}" = true ]; then
echo "新しいブランチ '${BRANCH_TO_USE}' を '${NORMALIZED_LOCAL_BRANCH}' から作成します..."
if git worktree add -b "${BRANCH_TO_USE}" "${WORKTREE_PATH}" "${NORMALIZED_LOCAL_BRANCH}"; then
echo -e "${GREEN}✓ Worktreeを作成しました: ${WORKTREE_PATH}${NC}"
echo -e "${GREEN}✓ 新しいブランチ '${BRANCH_TO_USE}' を作成しました${NC}"
else
echo -e "${RED}エラー: Worktreeの作成に失敗しました${NC}"
exit 1
fi
else
if git worktree add "${WORKTREE_PATH}" "${BRANCH_TO_USE}"; then
echo -e "${GREEN}✓ Worktreeを作成しました: ${WORKTREE_PATH}${NC}"
else
echo -e "${RED}エラー: Worktreeの作成に失敗しました${NC}"
exit 1
fi
fi
# ワークスペース設定ファイルを生成
generate_workspace_settings "${WORKTREE_PATH}" "${SELECTED_COLOR}"
# ウィンドウタイトルが指定されている場合、.code-workspaceファイルも生成
if [ -n "${WINDOW_TITLE}" ]; then
generate_code_workspace "${WORKTREE_PATH}" "${SELECTED_COLOR}" "${WINDOW_TITLE}"
fi
# VS Codeを開く
echo -e "${YELLOW}VS Codeを開いています...${NC}"
if command -v code >/dev/null 2>&1; then
# .code-workspaceファイルがある場合はそれを開く、なければフォルダを開く
if [ -n "${WINDOW_TITLE}" ] && [ -f "${WORKTREE_PATH}/workspace.code-workspace" ]; then
code -n "${WORKTREE_PATH}/workspace.code-workspace"
echo -e "${GREEN}✓ VS Code (.code-workspace)を開きました${NC}"
else
# フォルダを新しいウィンドウで開く(色設定が適用される)
code -n "${WORKTREE_PATH}"
echo -e "${GREEN}✓ VS Codeを開きました${NC}"
fi
else
echo -e "${YELLOW}警告: 'code' コマンドが見つかりません${NC}"
echo "VS Codeを手動で開いてください: ${WORKTREE_PATH}"
fi
echo ""
echo -e "${GREEN}=== 完了 ===${NC}"
echo "Worktreeパス: ${WORKTREE_PATH}"
if [ "${CREATE_NEW_BRANCH}" = true ]; then
echo "ブランチ: ${BRANCH_TO_USE} (新規作成)"
echo "ベースブランチ: ${BASE_BRANCH}"
else
echo "ブランチ: ${BRANCH_TO_USE}"
fi
echo ""
echo "Worktreeを削除する場合は以下のコマンドを実行してください:"
echo " git worktree remove ${WORKTREE_PATH}"
if [ "${CREATE_NEW_BRANCH}" = true ]; then
echo ""
echo "ブランチも削除する場合は以下のコマンドも実行してください:"
echo " git branch -D ${BRANCH_TO_USE}"
fi
VSCode Tasks から実行できるようにする
スクリプトは tasks.json に登録し、
VSCode のコマンドパレットから実行できるようにしました。
- Ctrl + Shift + P で起動
- プロンプト形式で必要な情報を入力
- 実行すると新しい Worktree が立ち上がる
プロンプトにはデフォルト値や前回値の保持も使えるため、
入力の手間はそれほど大きくありません。
tasks.json の設定例(クリックで展開)
{
"version": "2.0.0",
"tasks": [
{
"label": "Git Worktree 作成",
"type": "shell",
"command": "bash",
"args": [
"./scripts/create-worktree.sh",
"${input:baseBranch}",
"${input:worktreeName}",
"${input:createNewBranch}",
"${input:workspaceColor}",
"${input:windowTitle}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"group": {
"kind": "none",
"isDefault": false
}
}
],
"inputs": [
{
"id": "baseBranch",
"type": "promptString",
"description": "ベースブランチ名(例: main, base/feature-name)",
"default": "main"
},
{
"id": "worktreeName",
"type": "promptString",
"description": "Worktree名(例: issue/123-description)"
},
{
"id": "createNewBranch",
"type": "pickString",
"description": "新規ブランチを作成しますか?",
"options": ["y", "n"],
"default": "y"
},
{
"id": "workspaceColor",
"type": "pickString",
"description": "ワークスペースの色",
"options": ["", "blue", "green", "purple", "cyan", "orange"],
"default": ""
},
{
"id": "windowTitle",
"type": "promptString",
"description": "ウィンドウタイトル(オプション)",
"default": ""
}
]
}
Copilot カスタムプロンプトとの組み合わせ
それでも使っているうちに、
「入力自体が少し面倒」
と感じるようになりました。
そこで GitHub Copilot の カスタムプロンプトを用意しました。
- Issue 番号を渡す
- Copilot が Issue 内容を確認(GitHub Pull Requests 拡張機能を使用)
- ブランチ名やタイトルを提案
- 実行する bash コマンドを生成
実行提案の内容を確認し、
問題なければ 承認するだけで処理が進みます。
必要に応じて修正してから承認できるため、
安心して使える点も気に入っています。
Copilot カスタムプロンプトの例(クリックで展開)
---
mode: "agent"
description: "Issue番号からワークツリーとブランチを自動作成"
model: claude-haiku-4.5
---
あなたは Claude Haiku 4.5 ベースの AI アシスタントで Git Worktree とブランチ作成の専門家です。
Issue 番号を受け取り、以下の手順を実行してください:
## 入力
Issue 番号: ${input:issueNumber:Issue 番号を入力してください(例: 45)}
ベースブランチ: ${input:baseBranch:ベースブランチを入力してください(デフォルト: main)|default:main}
## 実行手順
1. **Issue 内容の確認**
- `github-pull-request_issue_fetch` ツールを使用して、指定された Issue 番号の内容を取得
- リポジトリは `YourOrg/your-repo` を使用(ご自身のリポジトリに置き換えてください)
- タイトルと本文から作業内容を理解する
2. **命名の考案**
以下のルールに従って名前を考案してください:
- **ブランチ名**: `issue/{issue番号}-{content-to-represent-in-english}`
- 例: `issue/45-sensor-integration`
- 英語で、ハイフン区切り、簡潔でわかりやすく
- **ウィンドウタイトル**: `Issue #{issue番号} - {日本語の作業内容}`
- 例:
- 要件定義: `Issue #45 - センサー統合機能の要件定義`
- 開発: `Issue #45 - センサー統合の開発`
- バグ修正: `Issue #78 - データ読み込みエラーの修正`
- 日本語で、作業内容が明確にわかるように
- **色の選択**:
- レビュー系: `purple`
- テスト系: `cyan`
- 実験系: `orange`
- 設計・要件定義: `blue`
- その他の開発: `green`
3. **コマンドの実行**
- 以下の情報をユーザーに通知する(確認は不要):
- **ベースブランチ**: `{ベースブランチ}`
- **新規ブランチ名**: `{ブランチ名}`
- **ウィンドウタイトル**: `{ウィンドウタイトル}`
- **色**: `{色}`
- `run_in_terminal` ツールで直接実行する:
```bash
bash ./scripts/create-worktree.sh {ベースブランチ} {ブランチ名} y {色} "{ウィンドウタイトル}"
```
- 例:
```bash
bash ./scripts/create-worktree.sh main issue/45-sensor-integration y blue "Issue #45 - センサー統合機能の設計"
```
- 実行結果(ワークツリーのパス、ブランチ名、VS Code の起動)を確認
## 重要な注意事項
- Issue 内容が取得できない場合は、エラーメッセージをユーザーに伝えること
- ブランチ名は既存のブランチと重複しないこと
- コマンド実行前に、実行内容を簡潔にユーザーに伝えること
## 成功基準
✅ Issue 内容を正しく取得できた
✅ 適切なブランチ名とウィンドウタイトルを考案できた
✅ コマンドが正常に実行された
✅ ワークツリーとブランチが作成され、VS Code が新しいウィンドウで開いた
補足:この仕組みは未完成でいい
ここで紹介したスクリプトやプロンプトは、
人によっては 不完全に感じると思います。
- 個人リポジトリ向けの例
- 業務では要件がもっと複雑になることもある
例えば、
- PR 番号からレビュー用 Worktree を起動したい
-
yarn installやyarn devを実行してから開きたい
といったケースもあると思います。
こうした部分は、
Copilot などの AI を使って少しずつ育てていくことで、
自分の業務に合った形にしていけると感じています。
まとめ
Git Worktree 自体は以前からある機能ですが、
そのまま使うよりも、
- スクリプト化
- VSCode との統合
- Copilot との組み合わせ
によって、ようやく業務に定着しました。
同じようなワークフローを繰り返している方の
何かしらのヒントになれば幸いです。
Discussion