Git Push/Pull 实操手册:从失败报错到安全协同
1. 这不是“又一个Git教程”,而是你真正能推上去、拉下来的实操手册
“Git Push and Pull Tutorial”——看到这个标题,很多人第一反应是:哦,又是那种点开三分钟就关掉的入门视频,讲完git add .和git commit -m "first commit"就急着让你去git push origin main,结果一执行就报错fatal: The current branch main has no upstream branch.,或者更常见的是,error: failed to push some refs to 'https://github.com/xxx/xxx.git',然后满屏红色文字,连错误在哪一行都找不到。我带过二十多个实习生,几乎每个人都在这个环节卡住超过两小时,不是因为命令记不住,而是根本不知道为什么push会失败、pull会覆盖、远程分支和本地分支到底是什么关系。这篇内容不讲Git历史,不画分布式图谱,也不堆砌--force-with-lease这种高阶参数。它只解决一件事:让你在真实协作场景中,每次push都能成功抵达远程仓库,每次pull都能安全合并最新代码,且清楚知道每一步在操作什么、影响谁、风险在哪。核心关键词就是:push、pull、origin、upstream、fast-forward、merge conflict、tracking branch。适合刚写完第一个console.log("Hello World")、正准备把项目传到GitHub的新手;也适合写了三年业务代码、但每次团队合代码都靠git reset --hard HEAD~3硬删历史的老手——因为你们卡在同一个地方:对push/pull背后的状态机一无所知。下面所有内容,全部来自我过去十年在七家不同技术栈公司(从嵌入式C到前端React再到Python数据平台)的真实协作现场记录,包括2023年某电商大促前夜,因git pull --rebase误操作导致CI流水线中断47分钟的复盘细节。现在,我们直接进入问题本质。
1.1 为什么90%的人学不会Push/Pull?根源不在命令,而在“状态盲区”
绝大多数Git教程失败的根本原因,是把Git当成一个“文件上传下载工具”,而它实际是一个本地状态快照管理系统。push和pull不是简单的“发文件”和“收文件”,而是两个本地仓库(你的机器和远程服务器)之间,基于提交哈希(commit hash)和引用(ref)的状态同步协议。举个生活化例子:你和同事共用一份纸质会议纪要,你负责A章节,他负责B章节。你写完A后,在页脚写上“已更新至2024-06-15版”,他写完B后也写“已更新至2024-06-15版”。但你们没约定“版本号怎么生成”,结果两人写的都是同一天,但内容完全不同。Git的push就是你把“自己这版纪要”和“你写的版本号(commit hash)”一起交给共享云盘;pull是你从云盘拿回“最新版纪要”和“最新版本号”。问题来了:如果云盘里存的是同事的B章节版本(hash:a1b2c3),而你本地是自己的A章节版本(hash:d4e5f6),此时你执行git push,Git会检查:你的d4e5f6是否以云盘的a1b2c3为父提交?如果不是(即你的版本不是从他的版本衍生出来的),Git默认拒绝——这就是non-fast-forward update错误。它不是在说“你不配上传”,而是在说“你俩的版本树已经分叉,必须先明确合并逻辑”。这个检查机制,就是Git保障协作安全的核心设计。而新手常犯的错误,是跳过git status和git log --oneline --graph --all这两步,直接敲push,等于闭着眼睛往高速路上并线。所以本篇所有操作,都将围绕可视化当前状态展开——不是教你怎么打字,而是教你如何看懂Git正在告诉你什么。
1.2 本教程的实操边界:只聚焦“最痛的5个场景”,拒绝知识泛滥
我删掉了所有非必要内容:不讲git rebase -i交互式变基,不讲git filter-branch删敏感信息,不讲.gitattributes行尾处理。因为真实工作中,95%的push/pull问题,集中在以下五个具体场景:
- 首次推送新仓库:本地初始化后,第一次
git push失败,提示no upstream branch; - 日常协作推送:修改代码后
git push被拒,报rejected non-fast-forward; - 拉取他人更新:
git pull后发现本地文件被覆盖,重要未提交代码消失; - 处理合并冲突:
git pull后出现CONFLICT,编辑器里全是<<<<<<< HEAD; - 修复错误推送:不小心
push了不该推的代码(如含密码的配置文件),如何安全撤回。
这五个场景,覆盖了从新人入职第一天到CTO发布紧急热修的所有高频痛点。每个场景,我会给出可直接复制粘贴的命令序列、每条命令执行前后的状态对比图(文字描述)、底层原理的三句话解释,以及我踩过的、文档里绝不会写的坑。比如,关于“首次推送”,几乎所有教程都说git push -u origin main,但没人告诉你:-u参数(即--set-upstream)的本质,是创建一个本地分支到远程分支的跟踪关系(tracking branch),这个关系存储在.git/config文件里,形如:
[branch "main"] remote = origin merge = refs/heads/main没有这个配置,Git就不知道git pull时该从哪个远程分支拉、git push时该推到哪个远程分支——它不是“忘了”,而是压根没被告诉。这种细节,才是你真正需要的“为什么”。
2. 核心机制深度拆解:Push/Pull背后的四层状态模型
要真正掌控push/pull,必须理解Git仓库内部的四层状态结构。这不是理论炫技,而是你每次看到报错时,能立刻定位问题根源的“诊断地图”。我把这四层,称为工作区 → 暂存区 → 本地仓库 → 远程仓库,它们像四个同心圆,数据只能按固定方向流动。
2.1 第一层:工作区(Working Directory)——你眼睛看到的文件
这是你用VS Code、Sublime或记事本直接编辑的文件所在目录。当你修改index.js,保存后,这个改动就存在于工作区。但此时Git完全不知情——它就像一个严格的图书管理员,只认“正式登记入库”的书,不认你随手写在便签纸上的笔记。工作区的状态,用git status查看,显示为modified(已修改)或untracked(未追踪)。关键点在于:工作区的任何改动,都不会自动进入Git的版本管理流程。很多人的“代码丢了”,就是因为改完文件没add,就直接pull,结果pull过程强制覆盖了工作区文件。记住:git pull=git fetch+git merge(或git rebase),而merge/rebase操作会修改工作区文件。如果你的工作区有未add的修改,Git会阻止merge(报错error: Your local changes to the following files would be overwritten by merge),但如果你用git pull --force(不存在此命令,但有人误以为有),或强行stash再pop,就可能覆盖。所以第一条铁律:永远在pull前执行git status,确认工作区干净(无modified/untracked)或已妥善处理。
2.2 第二层:暂存区(Staging Area / Index)——Git的“待入库清单”
暂存区是Git独有的概念,相当于一个临时中转站。执行git add index.js,不是把文件拷贝进去,而是把当前工作区index.js的快照(snapshot)的元数据(路径+哈希值)记录到暂存区。你可以把它想象成超市的购物篮:你把商品(文件)放进篮子(add),但还没结账(commit)。git status显示的Changes to be committed,就是这个篮子里的东西。暂存区的关键作用,在于精确控制哪些改动进入下一次提交。比如你同时改了index.js(功能代码)和.env(本地配置),你只想提交功能代码,就只git add index.js,.env留在工作区。此时执行git commit,只有index.js的快照被固化为一个新commit,.env的改动依然在工作区,不受影响。这解释了为什么git pull前要确保暂存区干净:如果暂存区里有未提交的改动,pull触发的merge会尝试将远程新commit与你暂存区的“待提交状态”合并,逻辑上极其混乱。所以第二条铁律:pull前,要么git add所有想保留的改动并commit,要么git stash暂存所有改动,切勿让暂存区处于半提交状态。
2.3 第三层:本地仓库(Local Repository)——你的私有版本时间轴
本地仓库位于项目根目录下的.git文件夹内,它存储了所有你commit过的快照、分支指针、标签等。每个分支(如main)本质上就是一个指向某个commit哈希的指针。执行git commit,Git会做三件事:1)计算工作区所有已add文件的哈希,生成一个新快照;2)创建一个新的commit对象,包含快照哈希、作者、时间、父commit哈希;3)将main分支指针移动到这个新commit上。所以main分支不是一条“线”,而是一个不断向前跳跃的“箭头”。git log --oneline显示的就是这个箭头跳过的所有落点。git push的本质,就是把这个“箭头”的最新位置(即main指向的commit哈希),连同这个commit依赖的所有父commit哈希,一起发送给远程仓库。远程仓库收到后,会检查:你发来的这个新commit,是否能通过其父commit,在它已有的commit图谱中找到一条路径?如果能(即你的新commit是以它最新commit为父节点),就是fast-forward,允许;如果不能(即你的新commit和它的最新commit是平行的两个叶子节点),就是non-fast-forward,拒绝。这就是第三条铁律:push能否成功,取决于你的本地main分支指针,与远程origin/main分支指针,是否构成一条直线(父子继承关系)。不是看文件内容,而是看commit哈希的拓扑关系。
2.4 第四层:远程仓库(Remote Repository)——团队共享的“中央图书馆”
远程仓库(通常叫origin)是一个独立的Git仓库,部署在GitHub、GitLab或公司内网服务器上。它和你的本地仓库完全平等,没有主从之分,只是大家约定俗成把它当作“权威源”。git push是把你本地的commit图谱“复制”一份到远程;git pull是把远程的commit图谱“复制”一份到本地,然后尝试合并。关键点在于:远程仓库本身没有“工作区”。你在GitHub网页上看到的文件,是它根据某个commit哈希实时渲染出来的视图,不是真实文件系统。所以,远程仓库永远不会出现“未提交的修改”,它只存储纯净的commit历史。这也意味着,push失败从来不是因为远程“磁盘满了”或“网络卡了”(那些会报连接错误),而是因为远程拒绝接受一个会破坏其历史线性结构的推送。理解这点,你就明白为什么git push --force如此危险:它不是“重试”,而是命令远程仓库“删除你原有的main指针,强行指向我给你的这个新commit”,这会抹掉其他同事基于旧main做的所有工作。所以第四条铁律:--force不是“解决push失败”的方法,而是“制造更大灾难”的开关,仅限单人仓库或明确协调后的紧急回滚。
3. 实操全流程详解:从零开始,亲手完成一次安全的Push/Pull循环
现在,我们抛弃所有假设,从一个真实的空白目录开始,一步步走完完整的协作生命周期。所有命令均在macOS/Linux终端或Windows Git Bash中验证,Windows CMD用户请将$提示符替换为>。我会标注每一步的预期输出、关键观察点和背后原理,让你不仅会操作,更懂它在做什么。
3.1 场景一:首次推送新仓库——解决“no upstream branch”报错
前提:你刚用git init初始化了一个本地仓库,写了几行代码,完成了第一次git commit,现在想推送到GitHub新建的空仓库。
错误操作(典型踩坑):
$ git push origin main fatal: The current branch main has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin main正确操作序列:
# 1. 确认当前分支和状态 $ git status On branch main Your branch is based on 'origin/main', but the upstream is gone. (use "git branch --unset-upstream" to fix this) nothing to commit, working tree clean # 2. 添加远程仓库地址(替换为你的真实URL) $ git remote add origin https://github.com/your-username/your-repo.git # 3. 执行首次推送,并设置上游分支(-u参数) $ git push -u origin main Enumerating objects: 3, done. Counting objects: 100% (3/3), done. Writing objects: 100% (3/3), 212 bytes | 212.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/your-username/your-repo.git * [new branch] main -> main Branch 'main' set up to track remote branch 'main' from 'origin'.关键解析:
git remote add origin ...:这行命令在.git/config中添加了一段配置,定义了名为origin的远程仓库地址。origin只是一个别名,你可以叫它upstream或github,但origin是行业惯例。git push -u origin main:-u(--set-upstream)是核心。它做了两件事:1)将本地main分支的commit推送到远程origin的main分支;2)在本地配置中建立main分支到origin/main的跟踪关系。执行后,.git/config会多出之前提到的[branch "main"]区块。* [new branch] main -> main:表示远程仓库上创建了一个全新的main分支,其起点就是你本地main的第一个commit。- 为什么必须用
-u?因为后续的git pull和git push(不带参数)才能知道该和谁同步。没有-u,下次你敲git push,Git会问:“你要推到哪个远程?哪个分支?”——它不知道。
实操心得:
提示:首次推送后,立即在GitHub网页上刷新,确认
main分支已存在,且README.md(如果有的话)内容正确。这是验证推送成功的黄金标准。我曾遇到过一次,git push命令行显示成功,但GitHub网页404,原因是远程URL末尾多了个.git(正确是https://github.com/u/r.git,错误是https://github.com/u/r.git.git),Git客户端不校验URL有效性,只管发包。
3.2 场景二:日常协作推送——应对“rejected non-fast-forward”
前提:你的同事小王在你push后,也向origin/main推送了他的修改。现在origin/main指向commita1b2c3,而你的本地main还停留在旧的d4e5f6。你修改了代码,commit后想push。
错误操作(典型踩坑):
$ git push origin main To https://github.com/your-username/your-repo.git ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'https://github.com/your-username/your-repo.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g., hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.正确操作序列(推荐git pull --rebase):
# 1. 先拉取远程最新变更 $ git pull --rebase origin main First, rewinding head to replay your work on top of it... Applying: add user login feature Using index info to reconstruct a base tree... M src/login.js Falling back to patching base and 3-way merge... Auto-merging src/login.js CONFLICT (content): Merge conflict in src/login.js error: Failed to merge in the changes. Patch failed at 0001 add user login feature hint: Use 'git am --show-current-patch' to see the failed patch Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: "git rebase --skip". To abort and get back to the state before "git pull", run "git rebase --abort". # 2. 解决冲突(编辑src/login.js,删除<<<<<<<, =======, >>>>>>>标记,保留正确代码) $ vim src/login.js # ... 编辑完成后保存 ... # 3. 将解决后的文件加入暂存区 $ git add src/login.js # 4. 继续rebase过程 $ git rebase --continue Successfully rebased and updated refs/heads/main. # 5. 现在可以安全推送 $ git push origin main Counting objects: 5, done. Delta compression using up to 8 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 324 bytes | 324.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0) To https://github.com/your-username/your-repo.git a1b2c3..f7g8h9 main -> main关键解析:
git pull --rebase origin main:pull默认是fetch + merge,--rebase将其改为fetch + rebase。rebase的意思是:把你本地main分支上,所有“不在远程origin/main上”的commit(即你的新commit),剪切下来,然后以远程最新的a1b2c3为新的起点,重新播放一遍。这样,你的commit历史就变成了一条直线:a1b2c3->your-new-commit-1->your-new-commit-2。CONFLICT:rebase过程中遇到冲突,是因为你的commit修改了src/login.js的某一行,而小王的commit也修改了同一行。Git无法自动决定留谁的,必须人工介入。git rebase --continue:告诉Git,冲突已解决,继续应用下一个commit。- 最终
push成功,显示a1b2c3..f7g8h9,表示远程main指针从a1b2c3移动到了f7g8h9,这是一个fast-forward更新。
实操心得:
注意:
--rebase会重写你本地commit的哈希值(因为父commit变了),所以f7g8h9和你原来的commit哈希不同。这是正常现象,不要恐慌。但这也意味着,如果你已经把旧commit哈希发给了同事(比如在IM里说“看我的commit d4e5f6”),那个哈希就失效了。因此,rebase只应在你自己的分支上使用,切勿对已push到公共分支的commit进行rebase。我见过最惨的一次,一位工程师对已push的main分支rebase并--force push,导致整个团队的本地历史全部错乱,花了三小时重置。
3.3 场景三:安全拉取他人更新——避免“未提交代码被覆盖”
前提:你正在开发一个新功能,工作区有未add的修改(git status显示modified),此时小王推送了新代码。你想拉取他的更新,但不想丢掉自己的改动。
错误操作(典型踩坑):
$ git pull origin main error: Your local changes to the following files would be overwritten by merge: src/utils.js Please commit your changes or stash them before you merge. Aborting正确操作序列(使用git stash):
# 1. 将工作区和暂存区的改动“藏起来” $ git stash push -m "WIP: user profile UI" Saved working directory and index state On main: WIP: user profile UI # 2. 现在可以安全拉取 $ git pull origin main From https://github.com/your-username/your-repo * branch main -> FETCH_HEAD Updating d4e5f6..a1b2c3 Fast-forward src/utils.js | 2 ++ 1 file changed, 2 insertions(+) # 3. 将“藏起来”的改动取出来 $ git stash pop On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/profile.js no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (e3f4g5...) # 4. (可选)如果取出来后有冲突,手动解决 $ vim src/profile.js $ git add src/profile.js $ git commit -m "Complete profile UI"关键解析:
git stash push -m "...":stash是一个栈(LIFO),push把当前工作区和暂存区的全部改动打包成一个临时commit,存入stash栈顶,并将工作区和暂存区恢复到上次commit的干净状态。-m参数添加描述,方便后续识别。git pull origin main:现在工作区干净,pull可以顺利执行fetch + merge,将小王的a1b2c3合并进来。git stash pop:从stash栈顶取出最近一次的改动,应用到当前工作区。如果应用过程有冲突(比如小王也改了src/profile.js),Git会像merge一样标记冲突,你需要手动解决。git stash list:查看所有stash记录,git stash apply stash@{1}可以应用指定序号的stash。
实操心得:
提示:
git stash是救急神器,但不是长期方案。我建议养成习惯:每天下班前,无论代码是否完成,都执行git add . && git commit -m "WIP: daily progress"。这样你的进度就固化在本地历史里,pull时无需stash,直接merge即可。stash只用于“临时切换上下文”的瞬间,比如老板突然让你紧急修复一个线上bug,你得马上放下手头功能。
3.4 场景四:处理合并冲突——读懂<<<<<<< HEAD的真正含义
前提:git pull或git merge后,文件中出现冲突标记。
冲突文件示例(src/login.js):
function login(username, password) { <<<<<<< HEAD // My change: add input validation if (!username || !password) return false; ======= // Xiao Wang's change: add logging console.log(`Login attempt for ${username}`); >>>>>>> a1b2c3 // Common code return api.login(username, password); }解决步骤:
- 理解标记含义:
<<<<<<< HEAD:表示“当前分支”(即你本地main)的代码块。=======:分隔线。>>>>>>> a1b2c3:表示“要合并进来的分支”(即小王的commita1b2c3)的代码块。
- 编辑文件:删除所有冲突标记(
<<<<<<<,=======,>>>>>>>及它们之间的行),只保留你想要的逻辑。在这个例子中,你可能想要同时保留输入验证和日志:function login(username, password) { // My change: add input validation if (!username || !password) return false; // Xiao Wang's change: add logging console.log(`Login attempt for ${username}`); // Common code return api.login(username, password); } - 标记为已解决:将修改后的文件
add到暂存区。$ git add src/login.js - 完成合并:
- 如果是
git pull(默认merge):执行git commit(Git会自动生成一个合并提交信息)。 - 如果是
git pull --rebase:执行git rebase --continue。 - 如果是
git merge:执行git commit。
- 如果是
实操心得:
注意:冲突解决后,
git add是强制步骤!很多新手编辑完文件就直接git commit,Git会报错nothing to commit, working tree clean,因为冲突文件仍处于“未合并”状态,必须先add才能解除冲突标记。另外,现代IDE(VS Code, WebStorm)都有图形化冲突解决工具,比手动编辑快十倍。在VS Code中,打开冲突文件,底部状态栏会出现“Accept Current Change”、“Accept Incoming Change”、“Accept Both Changes”按钮,点几下就搞定。
3.5 场景五:修复错误推送——安全撤回已发布的Commit
前提:你不小心push了一个含API密钥的config.js文件,现在想从远程仓库彻底删除它。
错误操作(典型踩坑):
# 危险!这会重写历史,影响所有协作者 $ git reset --hard HEAD~1 $ git push --force origin main正确操作序列(使用git filter-repo,安全且现代):
# 1. 安装git-filter-repo(比老的filter-branch更快更安全) $ pip install git-filter-repo # 2. 在本地仓库根目录,运行过滤命令(删除所有config.js文件的历史) $ git filter-repo --path config.js --invert-paths --force # 3. 强制推送到远程(此时是安全的,因为filter-repo已重写所有相关commit) $ git push --force origin main # 4. 通知所有协作者,让他们重置本地仓库 $ git fetch origin $ git reset --hard origin/main关键解析:
git filter-repo --path config.js --invert-paths:--path指定要操作的文件路径,--invert-paths表示“排除”这个路径,即从所有commit中删除config.js。--force是必需的,因为filter-repo会警告你正在重写历史。git push --force:此时--force是必要的,因为filter-repo生成了全新的commit哈希,远程仓库必须被强制更新。- 为什么比
reset安全?reset只回退你本地的main指针,而filter-repo会扫描整个历史,找到所有包含config.js的commit,并生成一个全新的、不含该文件的完整历史图谱。它更彻底,且是社区推荐的标准做法。
实操心得:
提示:
filter-repo不能撤销!执行前务必备份整个.git文件夹。另外,如果密钥已经泄露,filter-repo只是删除历史中的文件,必须立即在生产环境轮换该密钥。Git操作不能替代安全响应流程。我处理过三次类似事件,最快的一次,从发现到密钥轮换、历史清理、团队通知,全程22分钟。
4. 高频问题排查与避坑指南:来自真实战场的27个血泪教训
以下是我在代码审查、故障复盘和新人带教中,整理出的最常被问到、也最容易引发线上事故的27个问题。每个问题都附带一句话本质、标准排查步骤和独家避坑技巧。这些不是教科书答案,而是我亲眼见过、亲手解决过的“活案例”。
4.1 关于Push的12个致命问题
| 问题现象 | 一句话本质 | 标准排查步骤 | 独家避坑技巧 |
|---|---|---|---|
fatal: 'origin' does not appear to be a git repository | 远程仓库别名origin未配置或URL错误 | git remote -v查看配置;git remote add origin <correct-url>重新添加 | 技巧:在GitHub创建新仓库后,页面右上角的“Quick setup”区域,直接复制git remote add origin ...命令,不要手打。我因手误把github.com打成githib.com,调试了40分钟。 |
error: failed to push some refs to '...'且无其他提示 | 网络连接超时或防火墙拦截 | ping github.com;curl -I https://github.com;检查公司代理设置 | 技巧:在企业内网,git config --global http.proxy http://proxy.company.com:8080可能被全局设置,临时取消:git config --unset http.proxy。 |
remote: Permission to xxx/yyy.git denied to user(zzz) | SSH密钥未添加到GitHub,或HTTPS凭据过期 | ssh -T git@github.com测试SSH;git credential reject清除旧凭据 | 技巧:Mac用户用Keychain保存HTTPS密码,有时Keychain损坏会导致凭据失效,直接删掉“github.com”条目即可。 |
Updates were rejected because a pushed branch tip is behind its remote counterpart | 你的本地分支落后于远程,需先pull | git fetch origin;git log --oneline --graph --all查看分叉点 | 技巧:git fetch比git pull更安全,它只下载数据不合并,给你充分时间用git log分析。 |
error: src refspec main does not match any | 本地没有main分支,或拼写错误(如master) | git branch -a查看所有分支;git checkout -b main origin/main创建跟踪分支 | 技巧:GitHub新仓库默认分支是main,但老项目可能是master,git branch -r查看远程分支名最准确。 |
Everything up-to-date但GitHub上看不到新文件 | 文件被.gitignore忽略 | git check-ignore -v filename检查忽略规则 | 技巧:.gitignore支持通配符,node_modules/会忽略所有子目录,但!node_modules/react可反向取消忽略。 |
push后GitHub显示“0 commits”,但git log有记录 | 本地commit未关联到任何分支 | git status看是否在“detached HEAD”状态;git switch -c temp-branch创建新分支 | 技巧:git switch -c比git checkout -b更现代,是Git 2.23+推荐语法。 |
push大量文件时超时(RPC failed; curl 56) | HTTP请求体过大,被服务器或代理截断 | git config --global http.postBuffer 524288000(500MB);改用SSH协议 | 技巧:大文件应使用Git LFS,git lfs install后,git lfs track "*.psd"。 |
push后CI/CD流水线未触发 | GitHub Webhook未配置或失效 | 进入Repo Settings > Webhooks,检查URL和Secret | 技巧:Webhook测试用curl -X POST -H "Content-Type: application/json" -d '{"ref":"refs/heads/main"}' <webhook-url>。 |
push成功但分支保护规则(Branch Protection)阻止合并 | 你的账号无权限,或未满足CI检查 | git config --get-regexp branch.*.remote确认推送目标;检查GitHub Actions状态 | 技巧:分支保护规则下,push仍可成功,但PR无法合并,需联系Admin开通权限。 |
push后发现commit message写错 | 本地commit未推送,可用git commit --amend | git commit --amend -m "correct message";若已push,则git push --force-with-lease | 技巧:--force-with-lease比--force安全,它会检查远程分支是否被他人更新,防止覆盖他人工作。 |
push后发现漏推一个文件 | 该文件未被git add,或add后又被修改 | git status --ignored查看所有文件状态;git diff --cached查看暂存区内容 | 技巧:git add -A(添加所有)比git add .更可靠,它包含已删除文件。 |
4.2 关于Pull的15个隐形陷阱
| 问题现象 | 一句话本质 | 标准排查步骤 | 独家避坑技巧 |
|---|---|---|---|
pull后node_modules被删,项目无法启动 | package-lock.json冲突导致npm install失败 | git checkout --theirs package-lock.json;npm install | 技巧:package-lock.json应始终提交,它是npm install的 |
