ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

git push --mirror 报 pre-receive hook declined:TaoToken 场景下的排查与配置骨架

git push --mirror 报 pre-receive hook declined:TaoToken 场景下的排查与配置骨架 1. 从一次真实的git push --mirror翻车说起git push --mirror是仓库迁移、双活同步、灾备镜像场景里最省事的命令它会把本地镜像克隆里的所有 ref分支、标签、甚至refs/merge-requests/*这类隐藏引用原样推到目标仓库。但很多人第一次遇到pre-receive hook declined时会下意识以为是网络问题或者权限没配好反复重试、换 SSH key、甚至怀疑目标仓库挂了。实际上这个报错是目标仓库的服务端钩子主动拒绝跟你的网络、跟你的 key 权限往往没关系。这篇聚焦一个具体场景你用git clone --mirror把源仓库拉到本地再git push --mirror推到目标仓库结果所有分支被拒日志里出现类似The keywords cherry-pick and revert are branch names reserved by CodeHub, You are not allowed to create.的提示。我会把根因拆成三类——权限、分支保护、钩子脚本——并给出一套可复制的排查路径。同时结合 TaoToken 统一 Key/API 通道把 CI 或本地钩子脚本里调用模型做日志归因的配置骨架也一并交付方便你在批量迁移脚本里自动判断失败原因。适合谁看正在做仓库批量迁移的运维/后端、写 CI 同步流水线的工程师、以及被pre-receive hook declined卡住想快速定位根因的人。核心检索词就是git push --mirror、pre-receive hook declined、分支保护、钩子脚本。2. 先搞清楚pre-receive hook declined到底是谁在拒绝2.1 报错发生在服务端不在你的本地pre-receive是 Git 服务端在接收推送前执行的钩子。你的git push把对象和 ref 更新请求发过去服务端先跑这个钩子钩子返回非零退出码整个推送就被拒客户端看到的就是pre-receive hook declined。所以排查方向永远在目标仓库侧本地git config、ssh -T能通不代表推送能过。2.2 三类根因的区分方法根因类型典型现象快速判断权限不足提示You are not allowed to push、permission denied用同账号在 Web 端能否手动建分支分支保护提示受保护分支、protected branch看目标仓库分支保护规则列表钩子脚本提示保留分支名、自定义校验文案报错文案里带业务关键词如cherry-pick、revert--mirror的特殊之处在于它会推送所有 ref包括源仓库里那些临时分支、合并中间态分支。源仓库允许存在的分支名目标仓库的钩子可能明确禁止这就是本次报错的直接来源。2.3 为什么--mirror特别容易踩这个坑普通git push origin main只推一个分支钩子校验范围小。--mirror等于把源仓库的 ref 命名空间整体搬过去源仓库里合并请求产生的tmp-branch-xxxx、refs/merge-requests/*都会被尝试创建。目标仓库如果对分支命名有白名单/黑名单或者保留了cherry-pick、revert这类系统关键字就会在钩子层被拦下而且一个 ref 被拒会导致整批推送失败表现就是所有分支都被拒绝。3. TaoToken 前置把统一 Key 通道接进迁移脚本3.1 为什么迁移脚本里要接模型通道批量迁移时失败原因五花八门人工一条条看日志效率很低。我的做法是在迁移脚本的失败分支里把git push的 stderr 丢给模型做一次归因输出权限/分支保护/钩子脚本的分类和下一步建议。TaoToken 提供统一的 Key 和 API 通道本地脚本和 CI 用同一套配置即可不用为每个环境单独维护多套凭证。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址不带 UTMhttps://taotoken.net/api3.2 拿 Key 与选通道登录后进入控制台创建 API Key模型对话、Coding Plan、API Keys 各有独立入口按用途选只是脚本里做日志归因、单次问答用模型对话通道即可。长期跑 CI、Agent 类任务走 Coding Plan 更划算。需要程序化调用在 API Keys 页面生成 Key配合接入文档。控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注意Key 只放在环境变量或 CI Secret 里不要写进迁移脚本提交到仓库。下面骨架统一用TAOTOKEN_API_KEY读取。4. 可复制配置config.toml 与 settings.json 骨架4.1 config.toml 骨架本地/CI 通用# ~/.config/taotoken/config.toml # 统一 Key 通道配置迁移脚本与 CI 共用 [api] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 从环境变量读取不落盘 timeout_seconds 60 [model] # 日志归因用按需替换为可用模型名 name default max_tokens 1024 temperature 0.2 [git_mirror] # 迁移脚本相关参数 tmp_dir /tmp/git_mirror log_dir ./logs reserved_branch_keywords [cherry-pick, revert, tmp-branch]4.2 settings.json 骨架CI 侧{ taotoken: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, channel: coding-plan }, gitMirror: { cloneMode: mirror, pushMode: mirror, failFast: false, classifyOnError: true, reservedKeywords: [cherry-pick, revert, tmp-branch] } }4.3 迁移脚本里接入归因的关键片段#!/bin/bash # 在原有 push 失败分支里追加归因调用 push_mirror() { local target$1 local err if err$(git push --mirror $target 21); then echo [OK] push success: $target return 0 fi echo [FAIL] push rejected: $target echo $err | tee -a $LOG_FILE # 调用 TaoToken 做失败归因 curl -sS ${TAOTOKEN_BASE_URL:-https://taotoken.net/api}/chat/completions \ -H Authorization: Bearer ${TAOTOKEN_API_KEY} \ -H Content-Type: application/json \ -d $(jq -n --arg log $err { model: default, messages: [ {role: system, content: 你是 Git 排障助手只输出根因分类(权限/分支保护/钩子脚本) 下一步命令。}, {role: user, content: $log} ], temperature: 0.2 }) | jq -r .choices[0].message.content return 1 }这段逻辑不改变原有迁移流程只在失败时多打一份归因方便你批量跑的时候快速分流。5. 验证请求与一次完整的成功推送5.1 先验证 Key 通道可用export TAOTOKEN_API_KEY你的Key curl -sS https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:default,messages:[{role:user,content:ping}]} \ | jq -r .choices[0].message.content返回正常文本说明通道通了再往下做迁移验证。5.2 定位被拒的 ref# 进入镜像克隆目录 cd /tmp/git_mirror/repo1 # 列出所有 ref重点看 tmp 开头和保留关键字 git for-each-ref --format%(refname) | sort # 过滤可疑分支 git for-each-ref --format%(refname) | grep -E tmp-branch|cherry-pick|revert5.3 删除问题分支后重新推送# 删除本地镜像里的临时分支 git branch -D tmp-branch-xxxx # 方式一整体镜像推送 git push --mirror gittarget.example.com:group/repo1.git # 方式二只推分支命名空间避开 merge-requests 等隐藏 ref git push gittarget.example.com:group/repo1.git refs/heads/*:refs/heads/*方式二是我实测下来更稳的做法--mirror会把refs/merge-requests/*这类目标仓库不接受的引用也带上改成只推refs/heads/*就能绕开大部分钩子对隐藏 ref 的校验。5.4 成功结果长什么样Enumerating objects: 128, done. Counting objects: 100% (128/128), done. Delta compression using up to 8 threads Compressing objects: 100% (76/76), done. Writing objects: 100% (128/128), 45.21 KiB | 3.20 MiB/s, done. Total 128 (delta 52), reused 0 (delta 0) remote: Resolving deltas: 100% (52/52), done. To gittarget.example.com:group/repo1.git * [new branch] main - main * [new branch] release - release看到[new branch]且没有pre-receive hook declined说明钩子校验通过。6. 本篇常见错排查6.1 报错文案带保留关键字The keywords cherry-pick and revert are branch names reserved by CodeHub这类提示根因是钩子脚本不是权限。处理方式就是删掉源仓库里对应名字的临时分支或者改用refs/heads/*:refs/heads/*只推分支命名空间。6.2 只有部分分支被拒如果日志里只有个别分支失败先看是不是受保护分支。用git ls-remote对比源和目标git ls-remote --heads gitsource.example.com:group/repo1.git src.txt git ls-remote --heads gittarget.example.com:group/repo1.git dst.txt diff src.txt dst.txt6.3 权限类报错的判断# 测试 SSH 认证是否通过 ssh -T gittarget.example.com # 查看当前 remote git remote -v如果ssh -T返回欢迎语但 push 仍被拒基本可以排除认证问题转向分支保护和钩子脚本。6.4 钩子日志怎么定位服务端钩子日志通常在目标仓库的 hooks 目录或服务端日志里本地拿不到。你能做的是把git push的完整 stderr 保留下来用第 4.3 节的归因片段分类。常见钩子拒绝文案对照文案关键词根因reserved by、not allowed to create钩子脚本命名校验protected branch、not allowed to push分支保护permission denied、not authorized权限6.5 迁移脚本的清理逻辑别写错原脚本里cd $TMP_DIR rm -rf $REPO_NAME在 push 失败时也会执行导致问题现场被删。建议改成失败时保留目录if push_mirror $TARGET_REPO; then cd $TMP_DIR rm -rf $REPO_NAME else echo [KEEP] 保留现场: $TMP_DIR/$REPO_NAME fi7. 把归因通道固定进你的迁移流水线排查pre-receive hook declined的核心就一句话报错在服务端先分类再动手。权限、分支保护、钩子脚本三类根因的文案特征不同用第 6.4 节的对照表基本能秒判。--mirror的坑在于它会带上隐藏 ref 和临时分支改用refs/heads/*:refs/heads/*往往比删分支更快。如果你要把这套归因逻辑固化到 CI 或本地脚本里建议把 Key 通道统一走 TaoToken本地和流水线共用一份config.toml失败时自动分类省去人工翻日志。接入文档里有完整的请求格式和参数说明照着改curl那段就能跑通。
返回列表