refactor(scripts): 开发分支初始化脚本从 bash 重构为 Python,增强跨平台支持和用户体验
This commit is contained in:
354
scripts/init/init-dev-branch.py
Executable file
354
scripts/init/init-dev-branch.py
Executable file
@@ -0,0 +1,354 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""开发分支工作区初始化脚本
|
||||||
|
|
||||||
|
用于创建基于远端分支或新建的开发分支工作区。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_git(args: list[str]) -> subprocess.CompletedProcess:
|
||||||
|
"""执行 git 命令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: git 命令参数列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
subprocess.CompletedProcess 对象
|
||||||
|
"""
|
||||||
|
return subprocess.run(
|
||||||
|
["git"] + args,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_root_dir() -> Path:
|
||||||
|
"""获取 git 仓库根目录
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: 不在 git 仓库中
|
||||||
|
"""
|
||||||
|
result = run_git(["rev-parse", "--show-toplevel"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError("不在 git 仓库中")
|
||||||
|
return Path(result.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_remote() -> bool:
|
||||||
|
"""从远端获取最新分支信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功获取
|
||||||
|
"""
|
||||||
|
result = run_git(["fetch", "--quiet"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("警告: 无法获取远端信息,继续使用本地数据")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_remote_branches() -> list[str]:
|
||||||
|
"""获取所有远端分支列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
远端分支列表
|
||||||
|
"""
|
||||||
|
result = run_git(["branch", "-r"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [line.strip() for line in result.stdout.strip().split("\n") if line.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_branches(branch_name: str) -> list[str]:
|
||||||
|
"""获取远端匹配的分支列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 分支名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
匹配的远端分支列表
|
||||||
|
"""
|
||||||
|
result = run_git(["branch", "-r"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
line.strip()
|
||||||
|
for line in result.stdout.strip().split("\n")
|
||||||
|
if line.strip() and line.strip().endswith(f"/{branch_name}")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def branch_exists_local(branch_name: str) -> bool:
|
||||||
|
"""检查本地分支是否存在
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 分支名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否存在
|
||||||
|
"""
|
||||||
|
result = run_git(["show-ref", "--verify", "--quiet", f"refs/heads/{branch_name}"])
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def worktree_exists(worktree_path: Path) -> bool:
|
||||||
|
"""检查工作区是否存在
|
||||||
|
|
||||||
|
Args:
|
||||||
|
worktree_path: 工作区路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否存在
|
||||||
|
"""
|
||||||
|
result = run_git(["worktree", "list"])
|
||||||
|
if result.returncode != 0:
|
||||||
|
return False
|
||||||
|
return str(worktree_path) in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def extract_branch_name(remote_branch: str) -> str:
|
||||||
|
"""从远端分支名提取分支名
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote_branch: 远端分支名(如 origin/feature-123)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
分支名(如 feature-123)
|
||||||
|
"""
|
||||||
|
return remote_branch.split("/", 1)[1] if "/" in remote_branch else remote_branch
|
||||||
|
|
||||||
|
|
||||||
|
def validate_branch_creation(branch_name: str, worktree_path: Path) -> None:
|
||||||
|
"""验证是否可以创建分支和工作区
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 分支名称
|
||||||
|
worktree_path: 工作区路径
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: 验证失败
|
||||||
|
"""
|
||||||
|
if worktree_path.exists():
|
||||||
|
raise RuntimeError(f"工作区已存在于 {worktree_path}")
|
||||||
|
|
||||||
|
if worktree_exists(worktree_path):
|
||||||
|
raise RuntimeError(f"工作区 '{branch_name}' 已存在")
|
||||||
|
|
||||||
|
if branch_exists_local(branch_name):
|
||||||
|
raise RuntimeError(f"本地分支 '{branch_name}' 已存在")
|
||||||
|
|
||||||
|
|
||||||
|
def select_from_list(items: list[str], prompt: str, allow_create: bool = False) -> str | None:
|
||||||
|
"""让用户从列表中选择
|
||||||
|
|
||||||
|
Args:
|
||||||
|
items: 选项列表
|
||||||
|
prompt: 提示信息
|
||||||
|
allow_create: 是否允许创建新分支
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户选择的项,或 None 表示创建新分支
|
||||||
|
"""
|
||||||
|
if not items:
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(prompt)
|
||||||
|
for i, item in enumerate(items, 1):
|
||||||
|
print(f" {i}\t{item}")
|
||||||
|
|
||||||
|
if allow_create:
|
||||||
|
print(f" {len(items) + 1}\t创建新分支")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
max_choice = len(items) + 1 if allow_create else len(items)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
selection = int(input(f"请选择 (1-{max_choice}): "))
|
||||||
|
if 1 <= selection <= len(items):
|
||||||
|
selected = items[selection - 1]
|
||||||
|
print(f"已选择: {selected}")
|
||||||
|
return selected
|
||||||
|
if allow_create and selection == len(items) + 1:
|
||||||
|
return None
|
||||||
|
print(f"错误: 请输入 1-{max_choice} 之间的数字")
|
||||||
|
except ValueError:
|
||||||
|
print("错误: 请输入有效的数字")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n操作已取消")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def input_branch_name() -> str:
|
||||||
|
"""让用户输入分支名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
分支名称
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
name = input("请输入新分支名称: ").strip()
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
print("错误: 分支名称不能为空")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n操作已取消")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def create_worktree(branch_name: str, worktree_path: Path, base_branch: str | None = None) -> None:
|
||||||
|
"""创建工作区
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 新分支名称
|
||||||
|
worktree_path: 工作区路径
|
||||||
|
base_branch: 基础分支(可选)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: 创建失败
|
||||||
|
"""
|
||||||
|
args = ["worktree", "add", "-b", branch_name, str(worktree_path)]
|
||||||
|
if base_branch:
|
||||||
|
args.append(base_branch)
|
||||||
|
|
||||||
|
result = run_git(args)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"创建工作区失败: {result.stderr.strip()}")
|
||||||
|
|
||||||
|
|
||||||
|
def create_worktree_with_validation(
|
||||||
|
branch_name: str,
|
||||||
|
worktree_path: Path,
|
||||||
|
base_branch: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""验证并创建工作区
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 新分支名称
|
||||||
|
worktree_path: 工作区路径
|
||||||
|
base_branch: 基础分支(可选)
|
||||||
|
"""
|
||||||
|
validate_branch_creation(branch_name, worktree_path)
|
||||||
|
create_worktree(branch_name, worktree_path, base_branch)
|
||||||
|
print(f"工作区已创建于 {worktree_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_with_branch_name(branch_name: str, worktrees_dir: Path) -> None:
|
||||||
|
"""处理有分支名参数的情况
|
||||||
|
|
||||||
|
Args:
|
||||||
|
branch_name: 分支名称
|
||||||
|
worktrees_dir: 工作区目录
|
||||||
|
"""
|
||||||
|
worktree_path = worktrees_dir / branch_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_branch_creation(branch_name, worktree_path)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
remote_branches = get_remote_branches(branch_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if remote_branches:
|
||||||
|
selected_branch = select_from_list(
|
||||||
|
remote_branches,
|
||||||
|
"找到远端分支:",
|
||||||
|
allow_create=True
|
||||||
|
)
|
||||||
|
if selected_branch:
|
||||||
|
create_worktree(branch_name, worktree_path, selected_branch)
|
||||||
|
else:
|
||||||
|
create_worktree(branch_name, worktree_path)
|
||||||
|
else:
|
||||||
|
print("未找到远端分支,创建新分支")
|
||||||
|
create_worktree(branch_name, worktree_path)
|
||||||
|
|
||||||
|
print(f"工作区已创建于 {worktree_path}")
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_without_branch_name(worktrees_dir: Path) -> None:
|
||||||
|
"""处理无分支名参数的情况
|
||||||
|
|
||||||
|
Args:
|
||||||
|
worktrees_dir: 工作区目录
|
||||||
|
"""
|
||||||
|
remote_branches = get_all_remote_branches()
|
||||||
|
|
||||||
|
if not remote_branches:
|
||||||
|
print("未找到远端分支")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
selected_branch = select_from_list(
|
||||||
|
remote_branches,
|
||||||
|
"远端分支列表:",
|
||||||
|
allow_create=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected_branch:
|
||||||
|
branch_name = extract_branch_name(selected_branch)
|
||||||
|
worktree_path = worktrees_dir / branch_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_worktree_with_validation(branch_name, worktree_path, selected_branch)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
branch_name = input_branch_name()
|
||||||
|
worktree_path = worktrees_dir / branch_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_worktree_with_validation(branch_name, worktree_path)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="创建开发分支工作区",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
示例:
|
||||||
|
%(prog)s # 列出远端分支供选择
|
||||||
|
%(prog)s feature-123 # 创建 feature-123 工作区
|
||||||
|
%(prog)s bugfix-456 # 创建 bugfix-456 工作区
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
parser.add_argument("branch_name", nargs="?", help="分支名称(可选)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
root_dir = get_root_dir()
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
worktrees_dir = root_dir / ".worktrees"
|
||||||
|
worktrees_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
print("正在从远端获取最新分支信息...")
|
||||||
|
fetch_remote()
|
||||||
|
|
||||||
|
if args.branch_name:
|
||||||
|
handle_with_branch_name(args.branch_name, worktrees_dir)
|
||||||
|
else:
|
||||||
|
handle_without_branch_name(worktrees_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
echo "Usage: $0 <branch-name>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BRANCH_NAME="$1"
|
|
||||||
ROOT_DIR=$(git rev-parse --show-toplevel)
|
|
||||||
WORKTREES_DIR="$ROOT_DIR/.worktrees"
|
|
||||||
|
|
||||||
mkdir -p "$WORKTREES_DIR"
|
|
||||||
|
|
||||||
git worktree add -b "$BRANCH_NAME" "$WORKTREES_DIR/$BRANCH_NAME"
|
|
||||||
|
|
||||||
echo "Worktree created at $WORKTREES_DIR/$BRANCH_NAME"
|
|
||||||
Reference in New Issue
Block a user