随机
Enter 搜索 ↑↓ 切换 Esc 清空

git_repo_vis

脚本

Git 仓库公开/私有切换工具

补充说明

该脚本用于一键切换 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" 将私有仓库设为公开

效果预览

执行脚本效果预览

功能特点

交互流程

启动脚本
    ↓
检测环境变量 → 已设置 → 免交互模式 → 直接执行 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

  1. 登录 GitHub,进入 Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. 点击 Generate new token (classic)
  3. 勾选 repo 权限(Full control of private repositories)
  4. 点击 Generate token 生成令牌
  5. 复制生成的 ghp_xxxx 格式令牌

Gitee 私人令牌

  1. 登录 Gitee,进入 设置 → 私人令牌
  2. 点击 生成新令牌
  3. 勾选 projects 权限(项目的查看、创建、编辑、删除、 memberships、protected branches)
  4. 提交后复制生成的令牌

注意事项

脚本源码

#!/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"

相关命令