从零配置 GitHub SSH 到一键推送脚本:完整实践记录
2026-09-20T12:52:26+08:00 | 10分钟阅读

从零配置 GitHub SSH 到一键推送脚本:完整实践
背景
日常开发中,需要频繁向 GitHub 推送多个项目的代码。之前一直用 HTTPS + Personal Access Token(PAT)的方式,每次推送都要输入 Token,且 Token 明文写在脚本里,既不安全也不方便。
后来改用 SSH 密钥认证,配合自定义的推送脚本,实现了:
- 一次配置,永久免密
- 多个项目各自独立推送
- 双击即可完成 add → commit → push
本文记录整个配置过程,以及踩过的坑。
生成 SSH 密钥对
生成密钥
在任意目录打开命令行(CMD),执行:
ssh-keygen -t ed25519 -C "你的邮箱@example.com"
参数说明:
| 参数 | 含义 |
|---|---|
-t ed25519 | 使用 ed25519 算法(现代、安全、速度快,GitHub 推荐) |
-C "邮箱" | 添加注释,仅用于辨识,不参与加密 |
执行后会依次提示:
Enter file in which to save the key (C:\Users\你\.ssh\id_ed25519):
→ 直接回车,使用默认路径。
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
→ 直接回车(不设密码),或输入密码(更安全但每次要输)。
生成结果
密钥默认保存在 C:\Users\<用户名>\.ssh\ 目录下:
| 文件 | 说明 | 是否外传 |
|---|---|---|
id_ed25519 | 私钥 | ❌ 绝对保密 |
id_ed25519.pub | 公钥 | ✅ 添加到 GitHub |
一句话记忆:带
.pub的是公钥,可以公开;不带的是私钥,绝不能外传。

把公钥添加到 GitHub
添加至GitHub
用记事本打开
C:\Users\<用户名>\.ssh\id_ed25519.pub复制全部内容
登录 GitHub → 右上角头像 → Settings
左侧菜单 → SSH and GPG keys → New SSH key
粘贴内容,起个名字(如
MyPC),保存



验证连接
ssh -T git@github.com
第一次会提示是否信任主机,输入 yes。成功后显示:
Hi 用户名! You've successfully authenticated, but GitHub does not provide shell access.
看到这句话就说明 SSH 配置成功。


配置 SSH config 文件
如果只有一把id_ed25519密钥,可以不配置config文件。
需要 config的场景
默认情况下,SSH 会按固定顺序尝试 .ssh 下的默认命名密钥(id_rsa、id_ed25519 等)。如果你有多把密钥,或者使用了非默认命名(如 id_ed25519_nopass),就需要用 config 文件告诉 SSH 用哪把。
创建 config
路径:C:\Users\<用户名>\.ssh\config(无扩展名)
内容:
config:
Host github.com
HostName github.com
User git
IdentityFile C:/Users/<用户名>/.ssh/id_ed25519
IdentitiesOnly yes
注意事项:
- 文件名必须是
config,不能有.txt后缀 IdentityFile路径用正斜杠/,不要用反斜杠- 不要写行尾注释(如
IdentityFile xxx # 说明),SSH 会报garbage at end of line IdentitiesOnly yes强制只用指定的密钥,避免尝试其他密钥导致失败
验证
ssh -vT git@github.com
在输出里找:
debug1: Trying private key: C:/Users/.../id_ed25519
能看到,就说明 config 生效了。
让 SSH 不再问密码(可选)
如果生成密钥时设了密码,每次推送都要输。两种解决方案:
方案 A:使用 ssh-agent(推荐)
以**管理员身份**运行 PowerShell:
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
ssh-add C:\Users\<用户名>\.ssh\id_ed25519
输入一次密码后,后续就免输。
方案 B:生成无密码密钥
ssh-keygen -t ed25519 -C "邮箱" -f C:\Users\<用户名>\.ssh\id_ed25519_nopass
生成时两次回车跳过 passphrase。然后在 config 里把 IdentityFile 指向这个新密钥。
- 安全性:方案 A 更安全(私钥有密码保护),方案 B 更省事(无密码)。
- 个人电脑推荐 B,公共环境推荐 A。
修改项目的远程地址为 SSH
进入每个项目目录,执行:
git remote set-url origin git@github.com:用户名/仓库名.git
注意:
- SSH 地址格式是
git@github.com:用户名/仓库名.git - 不是
https://github.com/用户名/仓库名.git - 只需设置一次,之后永久生效
验证:
git remote -v
应该显示:
origin git@github.com:用户名/仓库名.git (fetch)
origin git@github.com:用户名/仓库名.git (push)
编写日常推送脚本
基础版本(单项目)
新建 push.bat:
@echo off
chcp 65001 >nul
cd /d E:\path\to\your\project
git add .
git commit -m "auto push %date% %time%"
git push
pause
说明:
| 命令 | 作用 |
|---|---|
@echo off | 关闭命令回显,屏幕更干净 |
chcp 65001 >nul | 切换 UTF-8 编码,避免中文乱码(>nul 隐藏切换提示) |
cd /d 路径 | 进入项目目录(/d 支持跨盘符) |
git add . | 暂存所有改动 |
git commit -m "..." | 提交,%date% %time% 自动填充时间 |
git push | 推送(无需写分支名,已建立过关联) |
pause | 结束后暂停,防止窗口一闪而过 |
多项目菜单版
当项目多了之后,与其每个项目一个脚本,不如做成菜单式脚本:
@echo off
chcp 65001 >nul
title 项目推送工具
:menu
cls
echo ============================================
echo 项目推送工具
echo ============================================
echo 1. ProjectA
echo 2. ProjectB
echo 3. ProjectC
echo --------------------------------------------
echo 0. 退出
echo ============================================
set /p choice=请输入项目编号并回车:
if "%choice%"=="1" goto ProjectA
if "%choice%"=="2" goto ProjectB
if "%choice%"=="3" goto ProjectC
if "%choice%"=="0" exit
goto menu
:ProjectA
set "PROJECT=ProjectA"
set "TARGET=E:\path\to\ProjectA"
goto push
:ProjectB
set "PROJECT=ProjectB"
set "TARGET=E:\path\to\ProjectB"
goto push
:ProjectC
set "PROJECT=ProjectC"
set "TARGET=E:\path\to\ProjectC"
goto push
:push
cls
echo ============================================
echo 正在推送: %PROJECT%
echo ============================================
cd /d "%TARGET%"
git add .
git commit -m "auto push %date% %time%"
git push
echo.
echo ============================================
echo 推送完成: %PROJECT%
echo ============================================
pause
goto menu
设计要点:
- 每个项目只定义
PROJECT(名字)和TARGET(路径) - 推送逻辑统一抽到
:push段,改一处等于改所有项目 - 推完
goto menu回到菜单,可连续推送多个项目
自动扫描版(进阶)
如果项目经常新增,硬编码列表每次都要改脚本,很麻烦。这时可以用自动扫描版:脚本运行时自动扫描指定根目录,找出所有 Git 仓库,动态生成菜单。
优点
- 新增项目后自动出现,无需改脚本
- 只维护两个配置项(扫描根目录路径)
- 适合项目多、变动频繁的场景
完整脚本
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
title 项目推送工具
:: ============================================
:: 配置:要扫描的根目录(可添加多个)
:: ============================================
set "ROOT1=E:\Informations\MSB\technical_information\git"
set "ROOT2=E:\Informations\DevelopmentProject\TraeCN\TraeCNProject"
:: ============================================
:menu
cls
echo ============================================
echo 项目推送工具
echo ============================================
echo 正在扫描 Git 项目...
echo.
set "INDEX=0"
set "LIST="
call :scan "%ROOT1%"
call :scan "%ROOT2%"
if "%INDEX%"=="0" (
echo 未找到任何 Git 项目。
echo 请检查根目录配置。
pause
exit /b
)
echo --------------------------------------------
echo 0. 退出
echo ============================================
set /p choice=请输入项目编号并回车:
if "%choice%"=="0" exit /b
:: 根据编号取出对应路径
set "TARGET="
for /f "tokens=1,2 delims=|" %%a in ("!LIST!") do (
if "%%a"=="%choice%" set "TARGET=%%b"
)
if "%TARGET%"=="" (
echo 无效输入,请重新选择。
timeout /t 2 >nul
goto menu
)
goto push
:: ============================================
:: 扫描函数:递归查找含 .git 的目录
:: ============================================
:scan
if not exist "%~1" exit /b
for /d %%D in ("%~1\*") do (
:: 第一层:检查是否本身是 Git 仓库
if exist "%%D\.git\" (
set /a INDEX+=1
echo !INDEX!. %%~nxD
set "LIST=!LIST!!INDEX!|%%D|"
)
:: 第二层:再往下一层查找(适配 DigitalPortal\DigitalPortal 结构)
for /d %%E in ("%%D\*") do (
if exist "%%E\.git\" (
set /a INDEX+=1
echo !INDEX!. %%~nxD / %%~nxE
set "LIST=!LIST!!INDEX!|%%E|"
)
)
)
exit /b
:: ============================================
:: 推送逻辑(所有项目共用)
:: ============================================
:push
cls
echo ============================================
echo 正在推送: %TARGET%
echo ============================================
echo.
cd /d "%TARGET%"
if errorlevel 1 (
echo [错误] 无法进入目录: %TARGET%
echo 请检查路径是否正确。
pause
goto menu
)
git add .
git commit -m "auto push %date% %time%"
git push
echo.
echo ============================================
echo 推送完成
echo ============================================
pause
goto menu
工作原理
- 扫描阶段:遍历
ROOT1、ROOT2两个根目录- 第一层:检查每个子目录是否含
.git - 第二层:再深入一层,适配"外层目录 + 内层仓库"的嵌套结构
- 第一层:检查每个子目录是否含
- 生成菜单:扫描时用
LIST变量累积"编号|路径",动态打印 - 选择推送:按编号从
LIST取出路径,执行统一推送逻辑 - 循环:推完
goto menu,回到菜单可继续选下一个项目
关键语法说明:
| 语法 | 作用 |
|---|---|
setlocal enabledelayedexpansion | 启用延迟变量展开,让循环里的 !变量! 实时更新 |
!INDEX! | 延迟展开语法(普通 %INDEX% 在循环里不会实时更新) |
for /d %%D in (...) | 遍历目录 |
if exist "%%D\.git\" | 判断该目录是否是 Git 仓库 |
%%~nxD | 取目录名(不含路径) |
| `for /f “tokens=1,2 delims= | “` |
使用注意:
- 脚本必须用 UTF-8 编码保存(配合
chcp 65001),否则中文菜单乱码 - 扫描深度为两层,如果项目嵌套更深需要再加一层循环
- 如果两个根目录下有同名项目,菜单会显示两个一样的名字,需注意区分
- 如某些目录不想被扫描(如备份),可在
if exist "%%D\.git\"里再加排除判断
自动扫描版 vs 硬编码版
| 对比项 | 硬编码版 | 自动扫描版 |
|---|---|---|
| 项目列表 | 手动写在脚本里 | 自动扫描目录生成 |
| 新增项目 | 需要改脚本 | 无需改脚本,自动出现 |
| 路径写死 | 是 | 否(由扫描得出) |
| 可移植性 | 换电脑要改 | 只改 ROOT 路径即可 |
| 复杂度 | 低 | 稍高,但一次配好 |
适用场景
- 项目固定、很少新增 → 硬编码版(简单直观)
- 项目经常新增、分散在多个根目录 → 自动扫描版(免维护)
批量全推版(进阶)
如果某天改动了多个项目,想一次性全部推上去,一个个选菜单太麻烦。这时可以用批量全推版:不需要菜单,运行后自动扫描并推送所有项目。
完整脚本
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
title 全部项目批量推送
:: ============================================
:: 配置:要扫描的根目录(可添加多个)
:: ============================================
set "ROOT1=E:\Informations\MSB\technical_information\git"
set "ROOT2=E:\Informations\DevelopmentProject\TraeCN\TraeCNProject"
:: ============================================
echo ============================================
echo 全部项目批量推送
echo ============================================
echo.
call :push_all "%ROOT1%"
call :push_all "%ROOT2%"
echo.
echo ============================================
echo 全部推送完成
echo ============================================
pause
exit /b
:: ============================================
:: 扫描并推送指定根目录下所有 Git 项目
:: ============================================
:push_all
if not exist "%~1" exit /b
for /d %%D in ("%~1\*") do (
:: 第一层
if exist "%%D\.git\" (
call :do_push "%%D"
)
:: 第二层
for /d %%E in ("%%D\*") do (
if exist "%%E\.git\" (
call :do_push "%%E"
)
)
)
exit /b
:: ============================================
:: 单个项目推送
:: ============================================
:do_push
echo.
echo ===== 推送 %~nx1 =====
cd /d "%~1"
if errorlevel 1 (
echo [错误] 无法进入目录: %~1
exit /b
)
git add .
git commit -m "auto push %date% %time%"
git push
exit /b
批量全推 vs 自动扫描版(菜单)
| 对比项 | 菜单版(7.3) | 批量版(7.4) |
|---|---|---|
| 交互 | 需要手动选择项目 | 全自动,无需选择 |
| 推送范围 | 只推选中的项目 | 推所有扫描到的项目 |
| 适用场景 | 只改了部分项目 | 改了很多项目,想一次全推 |
| 输出 | 一个项目一份结果 | 多个项目连续输出 |
运行效果示例:
============================================
全部项目批量推送
============================================
===== 推送 SubsTracker =====
nothing to commit, working tree clean
Everything up-to-date
===== 推送 MyraZ-blog =====
[main 3f2a1b0] auto push 2026/09/19 ...
2 files changed, 5 insertions(+)
To github.com:...
a1b2c3d..3f2a1b0 main -> main
===== 推送 DigitalPortal =====
nothing to commit, working tree clean
Everything up-to-date
============================================
全部推送完成
============================================
优缺点
优点:
- 一次运行,推完所有项目
- 新增项目后自动纳入,无需改脚本
- 适合"周末统一提交"或"换电脑前同步"等场景
缺点:
- 没改动的项目也会跑一遍
git add / commit / push,屏幕输出较多 - 如果某个项目 remote 配错,容易被淹没在大量输出里忽略
- 运行时间较长(项目越多越慢)
适用场景
| 场景 | 推荐 |
|---|---|
| 只改了一两个项目 | 菜单版(6.3) |
| 改了很多项目,想一次全推 | 批量版(6.4) |
| 定时任务/自动化脚本 | 批量版(无交互,适合无人值守) |
建议: 日常用菜单版,周期性(比如每周)用批量版统一提交一次,兼顾便利和可控。
脚本版本对比与选择建议
本文给出了四种脚本方案,从简单到复杂递进,可按需选择:
| 版本 | 位置 | 适用场景 | 维护成本 |
|---|---|---|---|
| 基础版(单项目) | 6.1 | 只有一两个项目 | 低,每个项目一个脚本 |
| 菜单版(硬编码) | 6.2 | 项目固定,很少新增 | 中,新增项目要改脚本 |
| 菜单版(自动扫描) | 6.3 | 项目较多、经常新增 | 低,只维护 ROOT 路径 |
| 批量全推版 | 6.4 | 改了很多项目,一次全推 | 低,只维护 ROOT 路径 |
我的实际使用方式:
- 日常零散改动 → 用 6.3 菜单版,双击选项目,推完回菜单继续
- 周期性统一提交(如每周一次)→ 用 6.4 批量版,一次推完所有项目
- 新增项目 → 放到扫描根目录下,脚本自动识别,无需任何修改
一次配置,长期受益。 配好之后,日常推送只是"双击 → 选编号 → 完成”,不再需要手动敲命令、输密码、改 remote。
分工原则:
| 类型 | 频率 | 位置 |
|---|---|---|
| SSH 密钥 + config | 一次 | ~/.ssh/ |
| remote 地址 | 每个项目一次 | 项目 .git/config |
| add / commit / push | 每次 | 推送脚本 |
常见问题汇总
把配置过程中踩过的坑集中列出,方便快速排查:
| 报错 | 原因 | 解决 |
|---|---|---|
fatal: not a git repository | cd 没进到项目目录 | 检查路径,cd /d 加引号 |
garbage at end of line; "#" | .ssh/config 写了行尾注释 | 删掉 #,或注释单独一行 |
src refspec main does not match any | 本地是 master,脚本推 main | 本地改名 git branch -M main,或脚本改 master,或统一用 git push |
Permission denied (publickey) | SSH 认证失败 | ssh -vT git@github.com 排查 |
nothing to commit, working tree clean | 没有改动 | 正常现象,不是错误 |
| 提交信息中文乱码 | .bat 编码与 CMD 不一致 | 另存为 ANSI,或提交信息用英文 |
Everything up-to-date | 本地与远程一致 | 正常现象,没有新内容可推 |
| SSH 每次要输密码 | 密钥设了 passphrase | 用 ssh-agent 缓存,或换无密码密钥 |
清理工作(可选)
配置成功后,可以清理旧的 HTTPS Token 和冗余密钥:
撤销 GitHub Token
最重要的一步,光删本地没用,必须去 GitHub 撤销:
- 登录 GitHub
- Settings → Developer settings
- Personal access tokens → Tokens (classic) 或 Fine-grained tokens
- 找到旧 Token → Delete / Revoke
检查各项目 remote
逐个项目确认 remote 是 SSH,没有残留HTTPS+Token:
cd /d 项目路径
git remote -v
显示 git@github.com:... 才正确。
清理 Windows 凭据管理器
按 Win + R,输入:
control /name Microsoft.CredentialManager
在 Windows 凭据 和 普通凭据 里,找名字含 git:https://github.com 的条目,删除。
清理冗余 SSH 密钥
如果同时存在旧的有密码密钥和新无密码密钥,确认 config 里 IdentityFile 指向正在用的那个后,删掉另一个:
# 查看 config 指向哪个
type C:\Users\<用户名>\.ssh\config
IdentityFile 指向的绝对不能删,其余可清理。
关键:光删本地没用,必须去 GitHub 上撤销 Token,否则 Token 依然有效。

湘公网安备43040002000293号