git push:如何把修改推送到远程仓库
git push 把本地分支中服务器尚未拥有的提交发送到远程仓库(通常名为 origin),并在服务器上移动该分支的指针。首次发布分支使用 git push -u origin 分支名,之后直接 git push 即可。如果服务器上有他人的提交,应先用 git pull 获取,而不是强制推送。
git push 的作用
命令 git push 会取出本地分支中、服务器上还没有的提交,把它们发送到远程仓库(通常名为 origin),并把那里该分支的指针移动到你的最新提交。工作目录中的文件和未提交的修改不会被发送,只有历史(提交)会被传输。
要注意,push 并不是“同步”。它只朝一个方向工作:从本地仓库到远程仓库。要获取别人的修改,需要使用 git fetch 或 git pull。如果把这两个方向搞混,几乎所有新手遇到的问题都会由此而来。
Git 还有一条规则:默认情况下,push 只允许“快进”(fast-forward)。只有当你的提交延续了服务器上已有的历史时,服务器才会接受。如果别人已经先推送了自己的提交,你的 push 会被拒绝——这是一种保护,而不是错误。
命令语法
git push [<远程>] [<本地分支>[:<远程分支>]]
不带参数执行 git push 时,Git 会取当前分支,并推送到其配置的上游(跟踪)分支。明确指定参数的形式适用于尚未配置上游,或者希望以不同名称推送分支的情况:
git push origin main # 把本地 main 推送到 origin/main
git push origin feature/login # 推送分支 feature/login
git push origin fix:hotfix-123 # 把本地 fix 以 hotfix-123 的名字推送到服务器
git push origin HEAD # 以相同的名字推送当前分支
第一次推送修改
从新项目到 GitHub 或 GitLab 上的代码,典型的路径如下。先在服务器上创建一个空仓库,然后把本地仓库与它关联并推送分支。
git init
git add .
git commit -m "First commit"
git branch -M main
git remote add origin git@github.com:user/project.git
git push -u origin main
参数 -u(完整写法 --set-upstream)会把本地分支 main 与远程的 origin/main 关联起来。之后只需输入 git push 和 git pull,git status 也会显示你领先或落后服务器多少个提交。
用 git remote -v 可以查看远程仓库指向哪里。如果地址写错了,请使用 git remote set-url origin <新地址> 修改,而不是删除并重新创建仓库。
如何推送新分支
你为某个任务创建了分支,做了提交,然后输入 git push。Git 回复:
fatal: The current branch feature/login has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/login
这并不是错误,而是提示:该分支在服务器上还没有对应的分支。执行提示中的命令,或者使用简写 git push -u origin feature/login 即可。若希望 Git 对所有新分支自动完成这一步,可以启用该配置(Git 2.37 起可用):
git config --global push.autoSetupRemote true
为什么 git push 被拒绝:rejected、non-fast-forward
团队协作中最常见的情况:
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:user/project.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
同事比你先推送了提交。你们的历史已经分叉,如果 Git 接受你的 push,同事的提交就会丢失。正确的做法是先获取他人的修改,并把你的提交放在它们之上:
git pull --rebase # 获取别人的提交,并把你的提交放到它们之上
# 如有冲突:修改文件,git add,git rebase --continue
git push
也可以使用普通的 git pull,这会产生一个合并提交。两种做法都是正确的;大多数团队对于自己的本地提交偏好 rebase,以保持历史线性。关键是不要用 --force “解决”这个拒绝。
git push --force 与 --force-with-lease
当你明确改写了自己分支的历史时,才需要强制推送:比如执行了 git commit --amend、git rebase,或者在评审前合并了提交。普通的 push 不会接受这样的分支,因为服务器上的旧提交已经不再是新提交的祖先。
git push --force-with-lease
--force-with-lease 只有在服务器上的分支仍位于你的本地 Git 上次看到的位置(即 origin/<分支> 引用指向的位置)时,才会覆盖它。如果同事在此期间推送了新内容,命令会拒绝执行,你就不会误删他们的工作。而普通的 --force(-f)没有这个检查,会悄无声息地覆盖一切。
注意一个细节:如果在此之前执行了 git fetch,origin/<分支> 引用会更新,保护机制也就失效了,因为 Git 会认为你已经“看到”了别人的提交。因此,不要在 fetch 之后直接强制推送,而应先看看拉取到了什么。
| 命令 | 作用 | 何时使用 |
|---|---|---|
git push | 只允许快进 | 默认情况下始终如此 |
git push --force-with-lease | 带检查的覆盖 | 自己的分支在 rebase 或 amend 之后 |
git push --force | 无检查的覆盖 | 几乎从不使用;绝不用于共享分支 |
不要对共享分支(main、develop)进行强制推送:所有已经拉取旧历史的人,都会与服务器上的历史产生分歧。这类分支通常还会通过仓库设置加以保护,服务器会返回 protected branch hook declined。
如何推送标签
普通的 git push 不会推送标签,需要显式指定:
git tag -a v1.2.0 -m "Release 1.2.0"
git push origin v1.2.0 # 推送单个标签
git push --tags # 推送所有本地标签
git push --follow-tags # 推送提交,以及指向这些提交的附注标签
--follow-tags 比 --tags 更方便:它不会推送偶然创建的轻量标签,只推送附注标签,并且只推送可以从待推送提交到达的那些。
如何删除服务器上的分支
删除同样通过 push 完成:
git push origin --delete feature/login
# 旧语法,效果相同:
git push origin :feature/login
本地分支仍然保留,可以用 git branch -d feature/login 删除。同事本地仍会保留 origin/feature/login 引用,直到他们执行 git fetch --prune。
git push 的常用参数
| 参数 | 作用 |
|---|---|
-u、--set-upstream | 把远程分支记为上游 |
-n、--dry-run | 显示将要推送的内容,但不实际推送 |
--all | 推送所有本地分支 |
--tags / --follow-tags | 推送标签 |
-d、--delete | 删除服务器上的分支或标签 |
--force-with-lease | 安全地改写历史 |
-v、--verbose | 详细输出 |
--no-verify | 跳过 pre-push 钩子(请谨慎使用) |
git push 的常见错误与解决方法
Permission denied (publickey)
服务器没有接受你的 SSH 密钥。检查密钥是否存在(ls ~/.ssh),公钥是否已添加到账户中,以及连接是否正常:ssh -T git@github.com。如果远程地址是 HTTPS 形式,Git 会要求输入用户名和令牌,而不是使用密钥——可以用 git remote -v 查看。
HTTPS 下出现 Authentication failed
GitHub 等服务早已不接受通过 HTTPS 使用账户密码。需要使用个人访问令牌,或改用 SSH。保存令牌可以借助凭据助手:git config --global credential.helper store(以明文保存),或使用操作系统的凭据管理器。
src refspec main does not match any
本地没有这个名字的分支。最常见的原因是空仓库中还没有任何提交,或者分支叫作 master。用 git branch 检查,然后提交一次,或者把分支重命名:git branch -M main。
显示 Everything up-to-date,但确实有修改
你忘记了提交。git push 推送的是提交,而不是文件。执行 git status,然后 git add 和 git commit。另一种可能是你并不在自己以为的那个分支上。
文件太大
服务器会限制单个文件的大小(GitHub 上是 100 MB)。如果这样的文件已经进入历史,仅仅用新提交删除它是不够的:需要改写历史,或者从尚未推送的提交中去掉它(git reset --soft,然后重新提交)。大型二进制文件应使用 Git LFS。
使用 git push 的工作循环
git switch -c feature/x:为任务创建分支。- 以小而合乎逻辑的步骤提交。
git fetch和git rebase origin/main:更新到最新的主分支。git push -u origin feature/x:发布分支。- 创建 pull request,评审之后追加提交,再普通
git push。 - 如果必须改写历史,使用
git push --force-with-lease。
推送之前,最好先看看将要上传什么:git log origin/main..HEAD --oneline 会列出服务器上还没有的提交。
一次性配置
一些全局配置能避免推送时的大部分意外。push.default 决定不带参数的 git push 做什么。在现代版本中,默认值是 simple:只推送当前分支,并且只推送到同名的上游分支。通常无需修改,但要检查旧配置中是否残留了 matching,它会一次推送所有同名分支。
git config --global push.default simple
git config --global push.autoSetupRemote true
git config --global push.followTags true
git config --global pull.rebase true
push.followTags 会让 --follow-tags 成为默认行为;pull.rebase 则在推送被拒绝、需要先获取别人的修改时,避免产生多余的合并提交。
pre-push 钩子
文件 .git/hooks/pre-push 会在推送之前执行。如果它以非零状态码退出,推送就会取消。团队常在其中运行测试或代码检查,防止有问题的代码被推送到服务器。只有在完全清楚钩子为什么失败时,才应使用 --no-verify 跳过它。
总结
git push 把你的提交发布到远程仓库,并且只朝一个方向工作。首次推送分支时使用 -u;被拒绝时,先通过 pull 获取别人的修改;只有改写了自己分支的历史后,才使用 --force-with-lease 推送。远程仓库、push、pull 和 SSH 密钥的使用,在 Git 课程中有实践讲解。
常见问题
git push 会推送未提交的修改吗?
不会。push 只传输提交;工作目录和暂存区中的修改会留在本地,直到你提交它们。
如何撤销一次 git push?
在自己的分支上,可以先在本地撤销提交(git reset),然后用 git push --force-with-lease 推送。在共享分支上,更安全的做法是用 git revert 创建一个反向提交,再普通推送。
能只推送一个提交吗?
可以,指定它的哈希即可:git push origin abc1234:main 会推送到该提交为止的全部历史,但不会推送之后的提交。无法只推送某个提交而不包括它的祖先。
git push 与 git push origin main 有什么区别?
不带参数时,Git 使用当前分支配置的上游;明确写出的形式直接指定远程和分支,即使没有配置上游也能工作。
如何同时推送到两个仓库?
添加第二个远程并分别推送,或者为 origin 添加多个推送地址:git remote set-url --add --push origin 地址(两个地址都要添加,包括原始地址,否则推送只会发往第二个地址)。