
Eliteforge Git Feature Oriented Spec
- 33 installs
- Updated July 24, 2026
- cloudsen/eliteforge-skills
Full Git spec for feature-oriented development with feature/bugfix/hotfix types, SemVer versions, and single- and multi-agent branch and worktree naming.
About
Defines the full feature-oriented branching model with feature/bugfix/hotfix types, SemVer versions, and release-flow branches, plus templates for non-multi-agent, base, and Codex subagent branches. A developer uses it to name branches and worktrees for EliteForge feature work.
- Branch templates include type, SemVer version, developer, and taskName
- Release-flow branches keep nightly, qa/<version>, release/<version>
Eliteforge Git Feature Oriented Spec by the numbers
- 33 all-time installs (skills.sh)
- Ranked #348 of 733 Git & Pull Requests skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cloudsen/eliteforge-skills --skill eliteforge-git-feature-oriented-specAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| Last updated | July 24, 2026 |
| Repository | cloudsen/eliteforge-skills ↗ |
What it does
Full Git spec for feature-oriented development with feature/bugfix/hotfix types, SemVer versions, and single- and multi-agent branch and worktree naming.
Files
EliteForge Git Feature-Oriented Spec
Environment Variables
ELITEFORGE_SKILL_GIT_WORKTREE_ROOT[optional] Root directory for generated git worktrees; defaults to${CODEX_HOME:-$HOME/.codex}/worktrees.
Branch Spec
main and master are equivalent
Follow the feature-oriented branching model. Continuously develop and test features.
Release-flow branches keep their project convention, such as nightly, qa/<version>, and release/<version>. Working branches only use feature, bugfix, or hotfix as <type>.
Non multi-agent branch name follows template: <type>/<version>/<developer>[/<taskId>]/<taskName>.
Multi-agent base branch name follows template: <type>/<version>/<developer>[/<taskId>]/<taskName>/base.
Codex subagent branch name follows template: <type>/<version>/<developer>[/<taskId>]/<taskName>/<agentId>/<subtaskName>.
- the
typeattribute only belongs to: - feature: new requirements
- bugfix: bugs does not stuck the main process
- hotfix: serious bugs requiring urgent fix
- the
versionattribute follows SemVer Specx.y.z developeris the name of the developer from git config, use email prefix if the name is not set.agentIdis not used in non multi-agent branches and is required for Codex subagents or parallel agents.- Subagent
agentIdmust use the role name only, for examplebackend,frontend,test,review, ordocs. - Put scope and target details in
taskNameorsubtaskName, not inagentId. - Do not use pure numbers, weak semantic names, numbered variants, or role-plus-target names such as
agent-01,backend-01,frontend-02,worker-a,tmp,work,backend-login-api,frontend-login-page, ortest-auth-flow. taskIdis optional, only used for non multi-agent branches when the project has task management system and the task can be easily linked to the branch name.taskNameandsubtaskNameshould be concise and descriptive, use hyphen to separate words, avoid using special characters.- Normalize each branch segment to lowercase filesystem-safe text using only
a-z,0-9,.,_, and-. - Do not use
/basefor non multi-agent workflows. Use/baseonly as the integration branch leaf after a task enters multi-agent workflow. - If a task starts on a non multi-agent branch and later needs subagents, rename the existing branch to the matching
/basebranch before creating subagent branches. Do not keep both forms for the same task.
| origin branch | target branch | ops | notes |
|---|---|---|---|
master | feature/<version>/<developer>[/<taskId>]/<taskName>、feature/<version>/<developer>[/<taskId>]/<taskName>/base、qa/<version>、release/<version> | create from master | development and unit testing |
master | feature/<version>/...、qa/<version>、release/<version> | git rebase | always rebase master latest commits |
<type>/<version>/<developer>[/<taskId>]/<taskName>/base | <type>/<version>/<developer>[/<taskId>]/<taskName>/<agentId>/<subtaskName> | create from multi-agent base branch | subtask implementation in isolated worktree |
<type>/<version>/<developer>[/<taskId>]/<taskName>/<agentId>/<subtaskName> | <type>/<version>/<developer>[/<taskId>]/<taskName>/base | git merge | subtask branch must merge back to its corresponding multi-agent base branch after completion |
feature | nightly | git merge | coding complete,no compilation errors |
feature | qa | create merge request | submit to testing, ensure version consistency |
feature | feature | git rebase -i (squash commits) && git push -f | squash mutiple commits of one feature |
feature(squashed) | release | git merge | ensure completed testing |
release | release | git tag <version> | ensure no snapshot/dev dependencies |
release | master | Fast-forward only merge | after merge completed,ensure all other branches for the current version are cleaned up |
Worktree Spec
When using git worktree, the worktree path must mirror the branch tree and have a one-to-one relationship with the checked-out branch.
Worktree root is resolved dynamically:
worktree_root="${ELITEFORGE_SKILL_GIT_WORKTREE_ROOT:-${CODEX_HOME:-$HOME/.codex}/worktrees}"- One worktree checks out exactly one spec-compliant branch.
- One spec-compliant branch must not be reused by multiple worktrees at the same time.
- Non multi-agent worktrees follow the non multi-agent branch tree:
<root>/<repo>/<type>/<version>/<developer>[/<taskId>]/<taskName>. - Multi-agent base worktree path follows template:
<root>/<repo>/<type>/<version>/<developer>[/<taskId>]/<taskName>/base. - Subagent worktree path follows template:
<root>/<repo>/<type>/<version>/<developer>[/<taskId>]/<taskName>/<agentId>/<subtaskName>. - Use the same semantic fields as the branch name; do not flatten path separators into a basename.
- Do not use project-ambiguous or weak worktree names such as
temp,new,work,scratch, orfeature-clouds3n-add-login. - If converting an existing non multi-agent worktree to multi-agent mode, move or recreate the worktree at the
/basepath before adding subagent worktrees. Do not create subagent worktrees inside an existing non multi-agent worktree.
Examples:
- non multi-agent branch
feature/1.2.0/clouds3n/add-loginuses worktree<root>/<repo>/feature/1.2.0/clouds3n/add-login. - multi-agent base branch
feature/1.2.0/clouds3n/add-login/baseuses worktree<root>/<repo>/feature/1.2.0/clouds3n/add-login/base. - subagent branch
feature/1.2.0/clouds3n/add-login/backend/session-tokenuses worktree<root>/<repo>/feature/1.2.0/clouds3n/add-login/backend/session-token.
Subagent Merge Spec
- The multi-agent
/basebranch is the integration branch for the task. - A subagent branch must merge back into its corresponding
/basebranch after the subtask is complete. - A subagent branch must not bypass the
/basebranch and merge directly into default,nightly,qa,release, or another release-flow branch. - The coordinating agent owns conflict resolution, final acceptance, and release-flow promotion.
Commit Spce
Follow Conventional Commits specification.
Merge Request Spce
Always use the default MR/PR template from current project.
Useful CLI Commands
Learn more from the scripts/ directory in this skilauto_merge: Merge the current version branches into the current branchcheck_merge: Check all branches for the current version are merged into the current branchdelete_local_branches: Delete all branches except the main/master.ensure all branches are pushedrename_git_branch: Rename the branch both locally and remotely
Quality Gates
- Check branch before coding. DO NOT develop directly on
master,main,nightly,qa,releasebranch - Non multi-agent branch name follows template:
<type>/<version>/<developer>[/<taskId>]/<taskName> - Multi-agent base branch name follows template:
<type>/<version>/<developer>[/<taskId>]/<taskName>/base - Subagent branch name follows template:
<type>/<version>/<developer>[/<taskId>]/<taskName>/<agentId>/<subtaskName> - Worktree path must mirror the branch tree under
${ELITEFORGE_SKILL_GIT_WORKTREE_ROOT:-${CODEX_HOME:-$HOME/.codex}/worktrees} - Worktree and branch must have a one-to-one relationship
- Subagent branches must merge back to the corresponding
/basebranch - Subagent
agentIdmust use a role name and must not include numeric suffixes, scope, or target details - Version follows
SemVer - Commit message follows
Conventional Commits
interface:
display_name: "eliteforge-git-feature-oriented-spec"
short_description: "Git specification for feature-oriented development with full branch types."
default_prompt: "Follow the EliteForge Git Feature-Oriented Spec."
#!/usr/bin/env bash
set -euo pipefail
########################################
# 参数解析
########################################
MODE="ff-only" # 默认模式:git pull --ff-only
MODE_SET=0
usage() {
cat <<EOF
用法:$(basename "$0") [--ff-only | --merge | --rebase]
--ff-only 执行 git pull --ff-only(默认行为),禁止产生 merge commit。
--merge 执行普通 git pull,允许非 fast-forward 合并。
--rebase 执行 git pull --rebase,以 rebase 方式更新当前分支。
-h, --help 显示本帮助。
示例:
$(basename "$0") # 等价于 --ff-only
$(basename "$0") --merge # 允许产生 merge commit 的 pull
$(basename "$0") --rebase # 以 rebase 方式更新当前分支
EOF
}
set_mode() {
local new_mode="$1"
if [[ $MODE_SET -eq 1 && "$MODE" != "$new_mode" ]]; then
echo "错误:检测到多种模式参数冲突(已选择:$MODE,又指定:$new_mode)。" >&2
usage
exit 1
fi
MODE="$new_mode"
MODE_SET=1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ff-only)
set_mode "ff-only"
shift
;;
--merge)
set_mode "merge"
shift
;;
--rebase)
set_mode "rebase"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "错误:未知参数:$1" >&2
usage
exit 1
;;
esac
done
########################################
# 1. 校验当前目录是否为 Git 仓库
########################################
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "错误:当前目录不是 Git 仓库,终止执行。"
exit 1
fi
# 保证工作区干净,避免来回切换分支时出事故
if [[ -n "$(git status --porcelain)" ]]; then
echo "错误:当前工作区或暂存区存在未提交变更,请先提交或暂存后再执行脚本。"
exit 1
fi
########################################
# 2. 检查当前分支
########################################
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")
echo "当前分支:$CURRENT_BRANCH"
# detached HEAD 情况直接拦截
if [[ "$CURRENT_BRANCH" == "HEAD" ]]; then
echo "错误:当前处于 detached HEAD 状态,请先切换到 qa/* 或 release/* 分支。"
exit 1
fi
# 3. 限制只能在 qa/* 或 release/* 上执行
if [[ ! "$CURRENT_BRANCH" =~ ^qa/ ]] && [[ ! "$CURRENT_BRANCH" =~ ^release/ ]]; then
echo "错误:当前分支不是 qa/* 或 release/* 分支,脚本终止执行。"
exit 1
fi
TARGET_BRANCH="$CURRENT_BRANCH"
########################################
# 4. 从分支名中解析版本号,例如 qa/2025.1.0 -> 2025.1.0
########################################
VERSION=$(echo "$TARGET_BRANCH" | cut -d'/' -f2)
if [[ -z "$VERSION" ]]; then
echo "错误:无法从分支名 $TARGET_BRANCH 中解析版本号(期望形式:qa/2025.1.0 或 release/2025.1.0)。"
exit 1
fi
echo "检测到版本号:$VERSION"
########################################
# 5. 更新目标分支
########################################
echo "拉取当前分支最新代码:$TARGET_BRANCH"
case "$MODE" in
ff-only)
echo "使用 fast-forward-only 模式:git pull --ff-only"
git pull --ff-only || {
echo "错误:git pull --ff-only 失败,请检查本地修改或远端状态。"
exit 1
}
;;
merge)
echo "使用 merge 模式:git pull"
git pull || {
echo "错误:git pull 失败,请检查本地修改或远端状态。"
exit 1
}
;;
rebase)
echo "使用 rebase 模式:git pull --rebase"
git pull --rebase || {
echo "错误:git pull --rebase 失败,请检查本地修改或远端状态。"
exit 1
}
;;
*)
echo "内部错误:未知 MODE=$MODE" >&2
exit 1
;;
esac
########################################
# 5.5 同步远程引用(便于发现新建的远程 feature/bugfix 分支)
########################################
echo "同步远程引用信息:git fetch --all --prune"
git fetch --all --prune
########################################
# 6. 查找要处理的分支(本地 + 远程)
########################################
echo "查找所有 feature/$VERSION/* 和 bugfix/$VERSION/* 分支..."
LOCAL_BRANCHES=$(git branch --format="%(refname:short)" | grep -E "^(feature|bugfix)/$VERSION/" || true)
REMOTE_BRANCHES=$(git branch -r --format="%(refname:short)" | grep -E "^origin/(feature|bugfix)/$VERSION/" || true)
# 只保留“远程上有而本地没有”的远程分支,避免重复
UNIQUE_REMOTE_BRANCHES=""
if [[ -n "$REMOTE_BRANCHES" ]]; then
while IFS= read -r RBR; do
[[ -z "$RBR" ]] && continue
local_name="${RBR#origin/}"
if ! echo "$LOCAL_BRANCHES" | grep -qx "$local_name"; then
UNIQUE_REMOTE_BRANCHES+="$RBR"$'\n'
fi
done <<< "$REMOTE_BRANCHES"
fi
if [[ -n "$LOCAL_BRANCHES" && -n "$UNIQUE_REMOTE_BRANCHES" ]]; then
ALL_BRANCHES="$LOCAL_BRANCHES"$'\n'"$UNIQUE_REMOTE_BRANCHES"
elif [[ -n "$LOCAL_BRANCHES" ]]; then
ALL_BRANCHES="$LOCAL_BRANCHES"
else
ALL_BRANCHES="$UNIQUE_REMOTE_BRANCHES"
fi
if [[ -z "${ALL_BRANCHES:-}" ]]; then
echo "未找到任何匹配版本 $VERSION 的 feature 或 bugfix 分支。"
exit 0
fi
echo "将要处理的分支(本地 + 远程)列表:"
echo "$ALL_BRANCHES"
echo "-------------------------------------"
########################################
# 7. 循环处理(更新分支并合并到目标分支)
########################################
for BR in $ALL_BRANCHES; do
echo "-------------------------------------"
# BR 可能是本地分支名(feature/...)或远程分支名(origin/feature/...)
if [[ "$BR" == origin/* ]]; then
LOCAL_BR="${BR#origin/}"
REMOTE_BR="$BR"
else
LOCAL_BR="$BR"
REMOTE_BR="origin/$BR"
fi
echo "处理分支:本地 = $LOCAL_BR 远程 = $REMOTE_BR"
# 检查远程是否存在
if git show-ref --verify --quiet "refs/remotes/$REMOTE_BR"; then
HAS_REMOTE=1
else
HAS_REMOTE=0
echo "提示:远程分支 $REMOTE_BR 不存在(可能尚未推送),仅使用本地分支。"
fi
# 确保本地分支存在;不存在则从远程创建
if git show-ref --verify --quiet "refs/heads/$LOCAL_BR"; then
echo "本地分支已存在:$LOCAL_BR"
else
if [[ $HAS_REMOTE -eq 1 ]]; then
echo "本地分支不存在,基于远程创建:$REMOTE_BR -> $LOCAL_BR"
git checkout -b "$LOCAL_BR" "$REMOTE_BR"
else
echo "警告:既不存在本地分支 $LOCAL_BR,也不存在远程分支,跳过。"
continue
fi
fi
# 更新本地分支(强制使用 rebase)
echo "切换到分支:$LOCAL_BR"
git checkout "$LOCAL_BR"
if [[ $HAS_REMOTE -eq 1 ]]; then
echo "使用 rebase 更新本地分支:git pull --rebase 跟踪 $REMOTE_BR"
# 确保 upstream 正确
if ! git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1; then
git branch --set-upstream-to="$REMOTE_BR" "$LOCAL_BR"
fi
git pull --rebase
else
echo "无对应远程分支,跳过 pull --rebase。"
fi
# 切回目标分支并合并
echo "切回目标分支:$TARGET_BRANCH"
git checkout "$TARGET_BRANCH"
echo "开始合并:$LOCAL_BR -> $TARGET_BRANCH"
if git merge --no-ff "$LOCAL_BR" -m "Merge branch '$LOCAL_BR' into $TARGET_BRANCH"; then
echo "✔ 合并成功:$LOCAL_BR"
else
echo "✘ 合并失败(可能存在冲突):$LOCAL_BR"
echo "请手动解决冲突后再继续。"
exit 1
fi
done
# 最终确保停留在目标分支
git checkout "$TARGET_BRANCH" >/dev/null 2>&1 || true
echo "✅ 所有符合版本 $VERSION 的分支已处理完毕并合并到 $TARGET_BRANCH"
#!/bin/bash
# 定义颜色
RED='\033[1;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# 检查是否提供了版本参数
if [ "$#" -ne 2 ]; then
echo "使用方法: $0 <version> <branch>"
exit 1
fi
version=$1
branch=$2
# 切换到 对应 分支并更新
git checkout ${branch}/$version || exit 1
git pull
# 刷新远程引用
git fetch --all
# 定义一个函数检查分支的合并状态
check_branch_status() {
local branch=$1
if git merge-base --is-ancestor "$branch" HEAD; then
echo -e "$branch 已合并"
else
echo -e "${RED}$branch 未合并${NC}"
fi
}
# 列出所有 feature/version 和 bugfix/version 分支并检查合并状态
for branch in $(git branch -r | grep -E "feature/$version|bugfix/$version"); do
check_branch_status "$branch"
done
#!/usr/bin/env bash
set -euo pipefail
KEEP_BRANCHES=("master" "main")
# 检查保留分支是否至少存在一个
keep_exist=false
for kb in "${KEEP_BRANCHES[@]}"; do
if git show-ref --verify --quiet "refs/heads/$kb"; then
keep_exist=true
break
fi
done
if [[ "$keep_exist" = false ]]; then
echo "错误:master 和 main 都不存在,无法继续。"
exit 1
fi
# 切换到存在的保留分支(优先 master)
for kb in "${KEEP_BRANCHES[@]}"; do
if git show-ref --verify --quiet "refs/heads/$kb"; then
git checkout "$kb"
break
fi
done
# 查询所有需要保留的分支(master/main)
keep_pattern=$(printf "|%s" "${KEEP_BRANCHES[@]}")
keep_pattern=${keep_pattern:1} # 去掉第一个 '|'
# 找到需要删除的分支
branches=$(git branch | grep -v "^\*" | grep -Ev "(${keep_pattern})" | sed 's/^[ \t]*//')
if [[ -z "$branches" ]]; then
echo "没有可删除的本地分支。"
exit 0
fi
echo "即将删除以下本地分支:"
echo "$branches"
echo
# 执行删除
for b in $branches; do
git branch -D "$b"
done
echo "完成:已删除所有非 master/main 的本地分支。"
#!/bin/bash
# 定义颜色
RED='\033[0;31m'
NC='\033[0m' # No Color
GREEN='\033[0;32m'
# 检查是否提供了版本参数
if [ "$#" -ne 1 ]; then
echo -e "${RED}错误: 需要提供一个版本号${NC}"
echo "使用方法: $0 <version>"
exit 1
fi
version=$1
# 切换到release分支
if git checkout release/$version; then
echo -e "${GREEN}切换到分支:release/$version${NC}"
else
echo -e "${RED}切换分支失败,可能不存在:release/$version${NC}"
exit 1
fi
# 刷新远程引用
git fetch --prune
# 列出所有feature/version分支,并检查合并状态
echo "检查已合并的分支:"
for branch in $(git branch -r | grep "feature/$version" | sed 's/origin\///'); do
if git merge-base --is-ancestor origin/$branch release/$version; then
echo -e "${GREEN}已合并: $branch${NC}"
# 确认删除
read -p "是否删除已合并的分支 $branch? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
git push origin --delete $branch
git branch -d $branch
echo -e "${RED}$branch 已删除${NC}"
fi
fi
done
#!/opt/homebrew/bin/bash
set -e
echo "# 📝 Change Log (relative to master branch)"
echo ""
# 1. 获取提交(排除 style, build, test)
commits=$(git log master..HEAD --pretty=format:"%h %s" | grep -vE "^(.* )?(style|build|test):")
# 2. 识别主提交(feat)
declare -A groups
declare -A commit_messages
while IFS= read -r line; do
hash=$(echo "$line" | awk '{print $1}')
message=$(echo "$line" | cut -d' ' -f2-)
if [[ "$message" =~ ^(feat)\(\#([0-9]+)\):[[:space:]](.*)$ ]]; then
id="${BASH_REMATCH[2]}"
title="${BASH_REMATCH[3]}"
groups["$id"]="### ✨ feat(#$id): $title"
fi
commit_messages["$hash"]="$message"
done <<< "$commits"
# 3. 二次分组:归类 fix/chore/refactor/... 提交
declare -A submessages
unmatched=""
while IFS= read -r line; do
hash=$(echo "$line" | awk '{print $1}')
message="${commit_messages[$hash]}"
# 跳过已作为主提交的 feat
if [[ "$message" =~ ^feat\(\#([0-9]+)\): ]]; then
continue
fi
if [[ "$message" =~ ^[a-z]+\(\#([0-9]+)\): ]]; then
id="${BASH_REMATCH[1]}"
if [[ -n "${groups[$id]}" ]]; then
submessages["$id"]+=$'\n'"- $message"
else
unmatched+=$'\n'"- $message"
fi
else
unmatched+=$'\n'"- $message"
fi
done <<< "$commits"
# 4. 输出分组 changelog(Markdown 格式)
for id in "${!groups[@]}"; do
echo -e "${groups[$id]}"
if [[ -n "${submessages[$id]}" ]]; then
echo -e "${submessages[$id]}"
fi
echo ""
done
# 5. 输出未归类的变更
if [[ -n "$unmatched" ]]; then
echo "### 🗂 Other Changes"
echo "$unmatched"
fi
#!/bin/bash
# 确保传入两个参数
if [ $# -ne 2 ]; then
echo "Usage: $0 <old-branch> <new-branch>"
exit 1
fi
old_branch=$1
new_branch=$2
# 检查旧分支是否存在
if ! git show-ref --verify --quiet refs/heads/$old_branch; then
echo "Error: Local branch '$old_branch' does not exist."
exit 1
fi
# 切换到旧分支
git checkout $old_branch
# 重命名本地分支
git branch -m $new_branch
# 推送新分支到远程,跳过 pre-commit 钩子
git push --no-verify origin $new_branch
# 设置新分支的上游分支,跳过 pre-commit 钩子
git push --no-verify --set-upstream origin $new_branch
# 删除远程的旧分支,跳过 pre-commit 钩子
git push --no-verify origin --delete $old_branch
# 清理本地的远程分支引用
git fetch -p
echo "Branch renamed from '$old_branch' to '$new_branch' successfully."
#!/bin/bash
# 检查参数数量是否足够
if [ "$#" -lt 3 ]; then
echo "用法: $0 <父目录路径> <作者> <开始日期>"
echo "例如: $0 /path/to/projects 'author1|author2|author3' '2023-11-01'"
exit 1
fi
# 将参数赋值给变量
BASE_DIR="$1"
AUTHORS="$2"
SINCE_DATE="$3"
# 总的代码行数统计
total_added=0
total_removed=0
total_loc=0
# 标题输出,确保列宽足够大
printf "%-30s %30s %30s %30s\n" "项目名称" "新增行数" "删除行数" "总行数"
echo "--------------------------------------------------------------------------------------------------------------"
# 遍历每个项目目录
for project in "$BASE_DIR"/*; do
if [ -d "$project/.git" ]; then
# 进入项目目录
cd "$project" || continue
# 使用 git log 统计当前项目的新增、删除和总行数
project_stats=$(git log --since="$SINCE_DATE" --no-merges --author="$AUTHORS" --pretty=tformat: --numstat \
| awk '{ add += $1; subs += $2; loc += $1 - $2 } END { print add, subs, loc }')
# 提取新增、删除和总行数
project_added=$(echo "$project_stats" | awk '{print $1}')
project_removed=$(echo "$project_stats" | awk '{print $2}')
project_loc=$(echo "$project_stats" | awk '{print $3}')
# 输出当前项目的行数统计
printf "%-30s %30d %30d %30d\n" "$(basename "$project")" "$project_added" "$project_removed" "$project_loc"
# 累加到总的统计数据
total_added=$((total_added + project_added))
total_removed=$((total_removed + project_removed))
total_loc=$((total_loc + project_loc))
fi
done
# 输出所有项目的总行数统计
echo "--------------------------------------------------------------------------------------------------------------"
printf "%-30s %30d %30d %30d\n" "所有项目的总行数统计:" "$total_added" "$total_removed" "$total_loc"