补充说明
该脚本用于一键切换 GitHub / Gitee 仓库的公开与私有状态,通过调用平台 REST API 实现仓库属性修改,支持交互式菜单选择和环境变量免交互两种模式,适合个人开发者和 CI/CD 自动化场景。
一键脚本
bash <(curl -sL gitee.com/meimolihan/cmdbox/raw/master/sh/git_repo_vis.sh)
bash <(curl -sL ../sh/git_repo_vis.sh)
参数示例
支持通过环境变量传参实现免交互执行,无需手动输入。
Gitee 免交互
# 公有仓库 → 私有仓库
GH_TOKEN="你的gitee私人令牌" \
REPO_API_URL="https://gitee.com/api/v5/repos/用户名/仓库名" \
REPO_LOCAL_PATH="/本地仓库路径" \
ACTION="public2private" \
PLATFORM="gitee" \
bash git_repo_vis.sh
# 私有仓库 → 公有仓库
GH_TOKEN="你的gitee私人令牌" \
REPO_API_URL="https://gitee.com/api/v5/repos/用户名/仓库名" \
REPO_LOCAL_PATH="/本地仓库路径" \
ACTION="private2public" \
PLATFORM="gitee" \
bash git_repo_vis.sh
GitHub 免交互
# 公有仓库 → 私有仓库
GH_TOKEN="你的github私人令牌" \
REPO_API_URL="https://api.github.com/repos/用户名/仓库名" \
REPO_LOCAL_PATH="/本地仓库路径" \
ACTION="public2private" \
PLATFORM="github" \
bash git_repo_vis.sh
# 私有仓库 → 公有仓库
GH_TOKEN="你的github私人令牌" \
REPO_API_URL="https://api.github.com/repos/用户名/仓库名" \
REPO_LOCAL_PATH="/本地仓库路径" \
ACTION="private2public" \
PLATFORM="github" \
bash git_repo_vis.sh
参数说明
| 参数 | 必填 | 说明 |
|---|---|---|
| GH_TOKEN | 是 | 平台个人访问令牌 (Personal Access Token) |
| REPO_API_URL | 是 | 仓库 API 地址 |
| REPO_LOCAL_PATH | 否 | 仓库本地绝对路径,留空则使用当前目录 |
| ACTION | 是 | 操作类型:public2private(公开→私有)或 private2public(私有→公开) |
| PLATFORM | 是 | 代码平台:github 或 gitee |
| 功能 | 说明 |
|---|---|
| 公开 → 私有 | ACTION="public2private" 将公开仓库设为私有 |
| 私有 → 公开 | ACTION="private2public" 将私有仓库设为公开 |
效果预览
功能特点
- 双平台支持:同时支持 GitHub 和 Gitee 两大主流代码平台
- 双向切换:支持 公开仓库 → 私有仓库 和 私有仓库 → 公开仓库 两种操作
- 两种运行模式:
- 交互式模式:通过菜单选择平台和操作,逐步输入令牌、路径、API 地址
- 免交互模式:通过环境变量传参,无需手动输入,适合脚本调用和 CI/CD 流水线
- 自动依赖管理:检测
jq是否安装,未安装时自动通过系统包管理器安装(支持 apt/dnf/yum/pacman) - 结果验证:执行完成后自动调用 API 查询仓库当前 private 状态并回显,确认操作是否生效
- 彩色输出:全程彩色日志提示,信息(蓝色)、成功(绿色)、警告(黄色)、错误(红色)清晰区分
- 交互菜单支持返回上一级和退出动画,操作体验流畅
- 本地路径可选:支持切换到仓库本地目录,也支持仅执行 API 操作不切换目录
交互流程
启动脚本
↓
检测环境变量 → 已设置 → 免交互模式 → 直接执行 API 调用
↓ 未设置
选择代码平台(1. GitHub / 2. Gitee / 00. 退出)
↓
选择操作类型(1. 公开→私有 / 2. 私有→公开 / 0. 返回上一级)
↓
输入配置信息:
├─ 平台个人令牌 (PAT)
├─ 仓库本地绝对路径(可选,回车使用当前目录)
└─ 仓库 API 地址
↓
调用 PATCH API 修改仓库 private 属性
↓
调用 GET API 查询当前 private 状态并显示结果
↓
操作完成,按任意键继续
输出说明
脚本输出包含以下字段:
| 字段 | 说明 |
|---|---|
| 平台选择 | 显示当前选择的代码平台(GitHub / Gitee) |
| 操作类型 | 显示当前执行的操作(公开→私有 / 私有→公开) |
| 令牌输入 | 提示输入平台个人访问令牌(不会显示在日志中) |
| 本地路径 | 显示仓库本地绝对路径,目录不存在时给出警告 |
| API 地址 | 显示输入的仓库 API 地址 |
| API 调用结果 | 显示 PATCH 请求是否成功(通过 jq 解析返回的 .id 字段判断) |
| private 状态 | 显示执行后仓库当前的 private 值(true / false) |
| 错误信息 | API 调用失败时显示完整返回内容,便于排查问题 |
参数说明(免交互模式)
通过环境变量传参可跳过所有交互步骤,直接执行 API 调用:
| 环境变量 | 必填 | 说明 |
|---|---|---|
GH_TOKEN |
是 | 平台个人访问令牌 (Personal Access Token) |
REPO_API_URL |
是 | 仓库 API 地址(见下方格式说明) |
REPO_LOCAL_PATH |
否 | 仓库本地绝对路径,留空则不切换目录 |
ACTION |
是 | 操作类型:public2private(公开→私有)或 private2public(私有→公开) |
PLATFORM |
是 | 代码平台:github 或 gitee |
API 地址格式:
| 平台 | API 地址格式 |
|---|---|
| GitHub | https://api.github.com/repos/用户名/仓库名 |
| Gitee | https://gitee.com/api/v5/repos/用户名/仓库名 |
免交互模式判断逻辑: 脚本启动时检测 GH_TOKEN、REPO_API_URL、ACTION、PLATFORM 四个环境变量是否均已设置,若全部非空则自动进入免交互模式,跳过交互菜单直接执行。REPO_LOCAL_PATH 为可选参数,设置后会自动 cd 到该目录。
令牌获取方法
GitHub Personal Access Token
- 登录 GitHub,进入 Settings → Developer settings → Personal access tokens → Tokens (classic)
- 点击 Generate new token (classic)
- 勾选
repo权限(Full control of private repositories) - 点击 Generate token 生成令牌
- 复制生成的
ghp_xxxx格式令牌
Gitee 私人令牌
- 登录 Gitee,进入 设置 → 私人令牌
- 点击 生成新令牌
- 勾选
projects权限(项目的查看、创建、编辑、删除、 memberships、protected branches) - 提交后复制生成的令牌
注意事项
- 需要平台的个人访问令牌 (PAT),确保拥有仓库管理权限
- GitHub Token 需要
repo权限,Gitee Token 需要projects权限 - 脚本依赖
jq工具解析 JSON 响应,未安装时会自动尝试通过 apt/dnf/yum/pacman 安装 - 自动安装
jq需要 root 权限,非 root 用户请提前手动安装 REPO_LOCAL_PATH目录不存在时脚本会发出警告但仍继续执行 API 操作,不影响切换结果- API 地址必须为完整 URL,不支持简写格式
- 令牌为空时脚本会报错并终止,请确保令牌已正确设置
- 交互模式下选择操作后输入
0可返回上一级菜单,输入00可退出脚本 - 修改仓库可见性为私有可能影响协作者和已有的克隆地址,请谨慎操作
- 修改仓库可见性为公开可能导致代码暴露,请确认仓库内容安全后再操作
- GitHub API 有请求频率限制(未认证 60 次/小时,认证后 5000 次/小时),频繁调用可能触发限流
- Gitee API 同样存在频率限制,建议避免短时间内大量调用
脚本源码
#!/bin/bash
set -uo pipefail
list_color_init() {
export gl_hui=$'\033[38;5;59m'
export gl_hong=$'\033[38;5;9m'
export gl_lv=$'\033[38;5;10m'
export gl_huang=$'\033[38;5;11m'
export gl_lan=$'\033[38;5;32m'
export gl_bai=$'\033[38;5;15m'
export gl_zi=$'\033[38;5;13m'
export gl_bufan=$'\033[38;5;14m'
}
list_color_init
log_info() { echo -e "${gl_lan}[信息]${gl_bai} $*"; }
log_ok() { echo -e "${gl_lv}[成功]${gl_bai} $*"; }
log_warn() { echo -e "${gl_huang}[警告]${gl_bai} $*"; }
log_error() { echo -e "${gl_hong}[错误]${gl_bai} $*" >&2; }
sleep_fractional() {
local seconds=$1
if sleep "$seconds" 2>/dev/null; then
return 0
fi
if command -v perl >/dev/null 2>&1; then
perl -e "select(undef, undef, undef, $seconds)"
return 0
fi
if command -v python3 >/dev/null 2>&1; then
python3 -c "import time; time.sleep($seconds)"
return 0
elif command -v python >/dev/null 2>&1; then
python -c "import time; time.sleep($seconds)"
return 0
fi
local int_seconds=$(echo "$seconds" | awk '{print int($1+0.999)}')
sleep "$int_seconds"
}
handle_invalid_input() {
echo -ne "\r\033[K${gl_huang}无效输入,1秒后返回${gl_bai}"
sleep_fractional 1
echo -ne "\r\033[K"
return 2
}
break_end() {
echo -e "${gl_lv}操作完成${gl_bai}"
echo -e "${gl_bai}按任意键继续${gl_bai} \c"
read -r -n 1 -s -r
echo ""
clear
}
exit_animation() {
local frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local dots=("${gl_hong}." "${gl_huang}." "${gl_lv}." "${gl_bufan}." "${gl_zi}.")
local dot_buffer=""
local frame_len=${#frames[@]}
local dot_idx=0
for ((i=0; i<20; i++)); do
if (( i > 0 && i % 3 == 0 && dot_idx < ${#dots[@]} )); then
dot_buffer+=${dots[$dot_idx]}
((dot_idx++))
fi
echo -ne "\r\033[K${gl_bufan}${frames[$((i%frame_len))]}${gl_bai} 正在退出 ${dot_buffer}${gl_bai}"
sleep_fractional 0.06
done
echo -e "\r\033[K${gl_lv}✓${gl_bai} 成功退出\n${gl_bai}"
clear
exit 0
}
cancel_return() {
local menu_name="${1:-上一级菜单}"
echo -ne "${gl_lv}即将返回 ${gl_huang}${menu_name}${gl_lv} ${gl_hong}.${gl_huang}.${gl_lv}.${gl_bai}"
sleep_fractional 0.6
echo ""
clear
}
install_deps() {
local pkg_list=("$@")
if [[ $EUID -ne 0 ]]; then
log_error "安装依赖需要root权限,请使用root或sudo执行"
return 1
fi
if command -v apt >/dev/null 2>&1; then
log_info "检测到Debian/Ubuntu系列,使用apt安装依赖"
apt update -qq >/dev/null 2>&1
apt install -y "${pkg_list[@]}"
elif command -v dnf >/dev/null 2>&1; then
log_info "检测到RHEL/CentOS/Fedora系列,使用dnf安装依赖"
dnf install -y "${pkg_list[@]}"
elif command -v yum >/dev/null 2>&1; then
log_info "检测到CentOS7系列,使用yum安装依赖"
yum install -y "${pkg_list[@]}"
elif command -v pacman >/dev/null 2>&1; then
log_info "检测到Arch系列,使用pacman安装依赖"
pacman -Syu --noconfirm "${pkg_list[@]}"
else
log_error "不识别当前系统包管理器,请手动安装: ${pkg_list[*]}"
return 1
fi
}
api_query_private_status() {
local platform="$1"
local token="$2"
local api_url="$3"
local curl_args=()
curl_args+=(-s -X GET)
if [[ "${platform}" == "github" ]];then
curl_args+=(-H "Authorization: token ${token}")
elif [[ "${platform}" == "gitee" ]];then
curl_args+=(-H "Authorization: Bearer ${token}")
fi
curl_args+=(-H "Accept: application/json")
curl_args+=("${api_url}")
local out
out=$(curl "${curl_args[@]}" 2>/dev/null)
echo "$out" | jq -r '.private' 2>/dev/null
}
change_repo_visibility() {
local platform="$1"
local token="$2"
local api_url="$3"
local private_flag="$4"
local curl_args=()
curl_args+=(-s -X PATCH)
if [[ "${platform}" == "github" ]];then
curl_args+=(-H "Authorization: token ${token}")
curl_args+=(-H "Accept: application/json")
curl_args+=(-H "Content-Type: application/json")
curl_args+=(-d "{\"private\":${private_flag}}")
elif [[ "${platform}" == "gitee" ]];then
curl_args+=(-H "Authorization: Bearer ${token}")
curl_args+=(-H "Accept: application/json")
curl_args+=(-H "Content-Type: application/json")
local repo_full="${api_url##*/repos/}"
local repo_name="${repo_full##*/}"
curl_args+=(-d "{\"name\":\"${repo_name}\",\"private\":${private_flag}}")
fi
curl_args+=("${api_url}")
local resp
resp=$(curl "${curl_args[@]}" 2>/dev/null)
echo "$resp"
}
local_git_check_status() {
if [[ ! -d .git ]];then
echo "NOTGIT"
return
fi
local remote_url
remote_url=$(git remote get-url origin 2>/dev/null || true)
echo "${remote_url}"
}
main_work() {
local GH_TOKEN="${GH_TOKEN:-}"
local REPO_LOCAL_PATH="${REPO_LOCAL_PATH:-}"
local REPO_API_URL="${REPO_API_URL:-}"
local ACTION="${ACTION:-}"
local PLATFORM="${PLATFORM:-}"
log_info "${gl_bai}DEBUG GH_TOKEN=[${gl_huang}${GH_TOKEN}${gl_bai}]"
log_info "${gl_bai}DEBUG REPO_API_URL=[${gl_huang}${REPO_API_URL}${gl_bai}]"
log_info "${gl_bai}DEBUG ACTION=[${gl_huang}${ACTION}${gl_bai}]"
log_info "${gl_bai}DEBUG PLATFORM=[${gl_huang}${PLATFORM}${gl_bai}]"
if [[ -n "${GH_TOKEN}" && -n "${REPO_API_URL}" && -n "${ACTION}" && -n "${PLATFORM}" ]]; then
log_info "检测到环境变量,启用免交互模式"
local private_target
if [[ "${ACTION}" == "public2private" ]]; then
private_target="true"
elif [[ "${ACTION}" == "private2public" ]]; then
private_target="false"
else
log_error "ACTION参数仅支持 public2private / private2public"
return 1
fi
if [[ "${PLATFORM}" != "github" && "${PLATFORM}" != "gitee" ]]; then
log_error "PLATFORM仅支持 github / gitee"
return 1
fi
if [[ -n "${REPO_LOCAL_PATH}" ]]; then
if [[ -d "${REPO_LOCAL_PATH}" ]]; then
cd "${REPO_LOCAL_PATH}" || true
fi
fi
log_info "执行修改仓库可见性,平台:${gl_lv}${PLATFORM}${gl_bai}"
local res
res=$(change_repo_visibility "${PLATFORM}" "${GH_TOKEN}" "${REPO_API_URL}" "${private_target}")
if echo "$res" | jq -e '.id' >/dev/null 2>&1; then
log_ok "API请求执行完成"
local cur
cur=$(api_query_private_status "${PLATFORM}" "${GH_TOKEN}" "${REPO_API_URL}")
local cn_status
if [[ "${cur}" == "true" ]];then
cn_status="私有仓库"
elif [[ "${cur}" == "false" ]];then
cn_status="公开仓库"
else
cn_status="未知(${cur})"
fi
log_info "当前仓库状态: ${gl_lv}${cn_status}${gl_bai}"
else
log_error "API调用失败,返回: ${res}"
return 1
fi
return 0
fi
clear
local git_ret
git_ret=$(local_git_check_status)
echo -e "${gl_huang}>>> 当前仓库的状态${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
if [[ "${git_ret}" == "NOTGIT" ]];then
echo -e "${gl_hong}非 Git 仓库${gl_bai}"
else
echo -e "${gl_lv}本地Git仓库${gl_bai}"
fi
echo ""
while true; do
echo -e "${gl_zi}>>> 仓库可见性切换工具${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_bufan}1. ${gl_bai}GitHub${gl_bai}"
echo -e "${gl_bufan}2. ${gl_bai}Gitee${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_hong}00. ${gl_bai}退出脚本${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
read -r -p "$(echo -e "${gl_bai}请选择代码平台: ${gl_bai}")" plat_opt
case "${plat_opt}" in
1)
PLATFORM="github"
break
;;
2)
PLATFORM="gitee"
break
;;
00)
exit_animation
;;
*)
handle_invalid_input
;;
esac
done
clear
echo -e "${gl_zi}>>> 输入配置信息 ${gl_huang}[${PLATFORM}]${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_bai}Gitee 创建令牌地址:${gl_lv}https://gitee.com/profile/personal_access_tokens${gl_bai}"
echo -e "${gl_bai}GitHub 创建令牌地址:${gl_lv}https://github.com/settings/tokens/new${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
read -r -p "$(echo -e "${gl_bai}请输入平台个人令牌(输入${gl_huang}0${gl_bai}返回): ${gl_bai}")" GH_TOKEN
if [[ "${GH_TOKEN}" == "0" ]]; then
cancel_return
main_work
return 0
fi
if [[ -z "${GH_TOKEN}" ]]; then
log_error "令牌不能为空"
break_end
return 1
fi
read -r -p "$(echo -e "${gl_bai}仓库本地绝对路径(回车使用当前目录,输入${gl_huang}0${gl_bai}返回): ${gl_bai}")" REPO_LOCAL_PATH
if [[ "${REPO_LOCAL_PATH}" == "0" ]]; then
cancel_return
main_work
return 0
fi
if [[ -z "${REPO_LOCAL_PATH}" ]]; then
REPO_LOCAL_PATH=$(pwd)
fi
if [[ -n "${REPO_LOCAL_PATH}" ]]; then
if [[ -d "${REPO_LOCAL_PATH}" ]]; then
cd "${REPO_LOCAL_PATH}" || log_warn "切换目录失败"
else
log_warn "本地目录 ${REPO_LOCAL_PATH} 不存在,跳过cd,不影响API调用"
fi
fi
read -r -p "$(echo -e "${gl_bai}输入仓库API地址(输入${gl_huang}0${gl_bai}返回): ${gl_bai}")" REPO_API_URL
if [[ "${REPO_API_URL}" == "0" ]]; then
cancel_return
main_work
return 0
fi
if [[ -z "${REPO_API_URL}" ]]; then
log_error "API地址不能为空"
break_end
return 1
fi
clear
local cur_status_raw
cur_status_raw=$(api_query_private_status "${PLATFORM}" "${GH_TOKEN}" "${REPO_API_URL}")
local cur_cn
if [[ "${cur_status_raw}" == "true" ]];then
cur_cn="私有仓库"
elif [[ "${cur_status_raw}" == "false" ]];then
cur_cn="公开仓库"
else
cur_cn="未知(${cur_status_raw})"
fi
echo -e "${gl_huang}>>> 当前仓库的状态${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_lv}${cur_cn}${gl_bai}"
echo ""
while true; do
echo -e "${gl_zi}>>> 选择操作 ${gl_huang}[${PLATFORM}]${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_bufan}1. ${gl_bai}公开仓库 → 私有仓库${gl_bai}"
echo -e "${gl_bufan}2. ${gl_bai}私有仓库 → 公开仓库${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
echo -e "${gl_huang}0. ${gl_bai}返回上一级选单${gl_bai}"
echo -e "${gl_bufan}————————————————————————————————————${gl_bai}"
read -r -p "$(echo -e "${gl_bai}请输入你的选择: ${gl_bai}")" opt
case "${opt}" in
1)
ACTION="public2private"
private_target="true"
break
;;
2)
ACTION="private2public"
private_target="false"
break
;;
0)
cancel_return
main_work
return 0
;;
*)
handle_invalid_input
;;
esac
done
log_info "开始调用接口修改仓库属性,平台:${PLATFORM}"
local api_resp
api_resp=$(change_repo_visibility "${PLATFORM}" "${GH_TOKEN}" "${REPO_API_URL}" "${private_target}")
if echo "${api_resp}" | jq -e '.id' >/dev/null 2>&1; then
log_ok "接口调用成功"
local now_status
now_status=$(api_query_private_status "${PLATFORM}" "${GH_TOKEN}" "${REPO_API_URL}")
local cn_now
if [[ "${now_status}" == "true" ]];then
cn_now="私有仓库"
elif [[ "${now_status}" == "false" ]];then
cn_now="公开仓库"
else
cn_now="未知(${now_status})"
fi
log_info "仓库当前状态: ${cn_now}"
else
log_error "接口调用失败,返回内容: ${api_resp}"
fi
break_end
}
main() {
if ! command -v jq >/dev/null 2>&1; then
log_warn "依赖 jq 未安装,尝试自动安装"
install_deps jq
if ! command -v jq >/dev/null 2>&1; then
log_error "jq安装失败,请手动安装jq"
exit 1
fi
fi
main_work
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main
fi
创建本地脚本
new_script="git_repo_vis.sh"
cat > "$new_script" << 'EOF'
#!/bin/bash
# 粘贴脚本源码
EOF
# 保留本地脚本,去掉 rm -f "$new_script"
chmod +x "$new_script" && ./"$new_script" && rm -f "$new_script"