ARTICLE DETAIL

资讯详情

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

Git远程仓库第一次推送:强制推送与初始化选项详解

Git远程仓库第一次推送:强制推送与初始化选项详解 1. 强推之前先搞清楚Git在怕什么远程空仓库与本地历史的第一次握手很多人第一次接触推送本地新建项目到Gitee或GitHub时都会遇到一个极其常见的报错failed to push some refs或者是Updates were rejected because the remote contains work that you do not have locally。然后网上搜教程回复千奇百怪有说删了重建的有说强推的有说拉下来合并的新手一脸懵。我结合Git、Gitee、GitHub的使用场景把这个问题掰开揉碎了讲透。先说结论新建的远程仓库在没有任何初始化文件时它是一条真正干净的空白历史线你的本地项目是一条完全独立的历史线。两条线第一次握手用强制推送是最省事、最安全的做法。1.1 为什么新建的远程仓库会拒绝你的推送要理解这个拒绝先要理解Git是怎么存储和比较历史的。Git世界里每一次commit都会生成一个SHA-1哈希值并且这个哈希值会带上父提交的哈希值。换句话说每一次提交都相当于链表里的一个节点整个提交历史是一条有向无环图。你本地有commit远程仓库也有commit这两条链是否相关取决于它们是否有共同的祖先节点。如果你在Gitee上新建仓库时没有勾选初始化仓库之类的选项那么远程仓库就是一个完全空白的仓库——它没有任何commit甚至没有分支。此时你执行git push本质上是告诉远程我这里有完整的一条历史线请把你那条空白历史线的指针指向我这边的顶端节点。这个操作在Git的设计里是被允许的。但问题在于很多平台新用户在创建仓库时会顺手勾选“自动生成README文件”“自动生成.gitignore”“自动生成开源许可证”。只要勾了任何一个远程仓库就立刻有了一个或多个commit它的历史线不再是空白而是有一个独立的起点。到了这一步你本地的历史和远程的历史就是两棵完全不相干的树。Git在没有共同祖先的情况下默认拒绝直接推送因为它不知道该以哪条线为准强行合并可能会造成无法预测的结果。于是你收到了failed to push some refs。英文提示里往往还写着maybe you want to integrate the remote changes first也许你应该先合并远程的改动。很多新手看到这句话就跑去执行git pull结果又遇到refusing to merge unrelated histories拒绝合并无关的历史——这就是第二个坑。1.2 强制推送到底做了什么git push -f也就是强制推送对应的长参数是--force。它的原理非常简单放弃远程仓库分支指针当前所指的commit直接把它推到本地分支的最新commit处。换句话说强制推送不是在合并两条历史线而是在覆盖。对一个新的、刚刚创建、除了平台自动生成的README或.gitignore之外什么都没有的仓库来说远程上那些自动生成的commit没有任何保留价值。强制推送一执行远程分支指针就直接指向你本地的历史线之前那些平台生成物相当于被从历史里摘除。这是不是意味着强制推送很危险分情况。如果你是在一个多人协作的成熟仓库上执行git push -f那危险是致命的——你等于把远程团队共同的历史改写成了你本地的那一条别人的提交可能瞬间消失。哪怕能用git reflog找回代价也非常高。但这里有个前提我们已经反复强调**这个仓库是你刚刚新建的、还没有人提交过有价值内容的仓库。**在这个场景下远程仓库里没有任何值得保留的东西强推的成本为零收益是省掉了处理无关历史合并的所有麻烦。1.3 为什么只有第一次才建议强力推送这个问题要看透两层一是远程仓库当前状态的确定性二是Git合并不相关历史的成本。第一次推送时远程仓库要么是完全空白要么只有平台自动生成的几个初始化文件。无论哪种远程的内容都是可再生且无价值的。此时使用强推心理负担可以完全放下。第二次及以后本地项目和远程仓库已经建立了同一个祖先历史。此时你只需要正常git pushGit能自动算出本地领先远程多少个commit、远程领先本地多少个commit执行的是一个干净的快速前进fast-forward或合并推送。这个阶段如果还使用强制推送就会抹掉远程上其他人的提交等于亲手制造事故。所以建议第一次强制推送的真正含义是在远程仓库还没有形成真正价值之前用最干脆的方式确立一个干净的共同起点。一旦起点确立之后的每一次push都应回归常规流程。2. 仓库初始化时那两个勾选框关掉才是正确操作Gitee和GitHub在创建新仓库时都会遇到一个选择页面要不要初始化README、要不要生成.gitignore、要不要添加许可证。有些新手觉得平台既然提供了就勾上省得自己写实际上这是一个需要谨慎对待的决定。2.1 自动生成的.gitignore真不如自己写平台提供的.gitignore模板是根据你选择的语言和框架自动生成的。听上去很方便实际用起来问题很多。第一模板里的规则往往过于笼统。以Java多模块项目为例平台生成的模板可能只包含target/、*.class这些基础项但你的项目可能还有本地配置目录、IDE目录、启动日志目录。这些没被.gitignore覆盖的文件一旦被误git add提交进仓库后面清理起来比一开始写规则要麻烦得多。第二模板是别人给的不是你验证过的。我在实际项目里遇到过不止一次依赖平台模板生成的.gitignore把某些关键目录忽略掉了导致别人拉取代码后编译缺少必要文件。这类问题排查起来非常隐蔽。第三不同平台、不同语言的模板质量参差不齐。与其依赖平台生成不如自己在本地项目里写一份只属于这个项目的.gitignore。你会发现写规则的过程也是梳理项目结构的过程。2.2 LICENSE不是随便选的开源许可证LICENSE是一个有法律意义的内容不是随手选一个就行的。平台给你的选项包括MIT、Apache 2.0、GPL-3.0、BSD、LGPL等。每种许可证对使用者的约束完全不同。举个典型例子MIT许可证非常宽松允许别人自由使用、修改、分发你的代码甚至可以闭源商用而GPL-3.0带有强传染性只要用了你的代码对方整个项目都大概率需要以GPL方式开源。对于一个刚起步的本地项目你可能还没想清楚这个项目要不要开源、要不要商用、别人能不能随便抄。这时候让平台自动生成一个LICENSE相当于在一张空白合同上随手签了名。我个人的建议是新建仓库时什么都不勾选等项目的定位、授权模式想清楚了再手动添加LICENSE文件。2.3 如果已经勾选了自动生成文件怎么补救如果你创建的仓库已经勾了READEME或.gitignore别慌。因为是新建仓库补救成本非常低只有两种处理方式。方式一丢弃远程初始化文件本地直接覆盖如果远程仓库只是在创建时自动生成了README.md、.gitignore或LICENSE仓库里没有任何其他人提交的内容那你直接忽略它就好。本地完成git commit之后执行git push -u origin main --force强推会让远程分支指针直接指向你本地的历史之前自动生成的那些文件不再成为远程主线的组成部分。操作前记得确认一下远程仓库是否有别人提交的内容这一步很重要。如果这是你一个人刚建的仓库放心干。方式二拉取远程初始化文件并合并如果你想保留平台自动生成的README等内容比如想让仓库展示页有个介绍那可以用合并方式处理。先拉取远程历史并显式允许无关历史合并git pull origin main --allow-unrelated-histories这一步会把远程自动生成的README.md等文件和你的本地文件合并到一个工作区里。如果两边都创建了同名文件比如你都写了一个README.mdGit会停下来让你手动解决冲突。处理完冲突后git add . git commit -m merge remote init再正常git push即可。比较两种方式我几乎总是推荐第一种。原因简单纯粹新建仓库的自动化初始文件不值得你为它们做一次合并操作。3. 从零到一本地新项目推送Gitee/GitHub的完整步骤这一节把完整的流程走一遍。无论你用的是GitHub还是Gitee步骤都是一样的只是仓库地址的域名不同。这里我以Gitee为例GitHub用户把域名换成github.com即可。3.1 基础环境安装Git与全局配置Windows直接下载Git官方安装包。安装时会询问调整PATH的方式建议选择Git from the command line and also from 3rd-party software这样在cmd、PowerShell里都能用。其余选项保持默认即可。macOS如果装了Homebrew执行brew install git。没有Homebrew的可以直接下载官方安装包。LinuxDebian/Ubuntu系sudo apt update sudo apt install git。CentOS系列用sudo yum install git。安装完成后先做全局身份配置。这一项如果不做Git会直接拒绝commit或者用一串莫名其妙的名字记录提交。git config --global user.name 你的名字 git config --global user.email 你常用的邮箱为什么要配成全局因为Git每次提交时都要记录作者和提交者信息。不配置后续提交会失败报错Please tell me who you are。全局配置的好处是只要不特意指定这台机器上所有仓库的默认提交身份都是你。3.2 SSH密钥的生成与平台绑定推送代码有HTTPS和SSH两种通道。HTTPS需要每次输入账密或者配置tokenSSH在绑定密钥后可以实现免密推送。我建议直接用SSH稳定而且省事。先检查本机是否已经有SSH密钥ls -al ~/.ssh如果看到id_ed25519.pub或者id_rsa.pub这类文件说明你已经生成过密钥。如果没有执行生成命令ssh-keygen -t ed25519 -C 你常用的邮箱这里推荐Ed25519算法比RSA更安全生成的密钥也更短。命令执行后会问你保存位置和口令直接一路回车即可生成的默认位置是~/.ssh/id_ed25519。接下来查看公钥内容cat ~/.ssh/id_ed25519.pub复制这段公钥登录Gitee后进入设置-安全设置-SSH公钥粘贴并保存。GitHub上的入口是Settings-SSH and GPG keys-New SSH key。测试一下是否绑定成功ssh -T gitgitee.com如果是GitHub则是ssh -T gitgithub.com首次连接会提示确认指纹输入yes回车即可。看到欢迎信息说明密钥绑定成功可以走后续流程了。3.3 新建仓库关键的选项不要选在Gitee或GitHub上点击新建仓库填写仓库名称、描述选择公开或私有。注意仓库初始化选项那一栏README、.gitignore、License全部不要勾选。这也是本文最核心的一条操作建议。让远程仓库保持一个真正的空状态后续推送会干净利落。创建完成后你会得到一个远程仓库地址形如gitgitee.com:你的用户名/你的仓库名.git记下来下一步要用。3.4 本地项目初始化与首次推送进入你的本地项目根目录。这里我把项目目录假设为~/my-project。cd ~/my-project git init执行git init之后项目目录里出现一个隐藏的.git文件夹这就是Git的本地版本库。接着添加所有文件到暂存区git add .然后提交。提交信息建议写清楚第一次可以写init project实在不知道写什么的写first commit也比空白强。git commit -m init project接下来关联远程仓库。远程仓库的默认名字习惯叫origingit remote add origin gitgitee.com:你的用户名/你的仓库名.git关联完成后需要确认本地分支名称。Git在不同版本、不同机器上的初始分支名不同有的叫master有的叫main。现在Gitee新建仓库的默认分支通常是masterGitHub是main。为了对齐远程可以在推送前把本地分支改名git branch -M master如果你用的平台默认分支是main就把命令里的master换成main。执行git branch -M的作用是如果分支名不对就改名如果正确就跳过。最后执行第一次推送git push -u origin master --force参数拆开讲-u是--set-upstream的缩写把本地分支和远程分支关联起来。之后你在这个分支上直接git push不用再写远程分支名。--force是强制推送。前面说过第一次推送用强推可以完全跳过不相干历史的处理。origin master表示推送到origin远程的master分支。命令跑完刷新Gitee或GitHub仓库页面本地代码应该已经全部出现在远程仓库里了。到了这一步本地与远程的第一次握手就完成了。3.5 验证推送结果推送完成后验证是必要动作。观察三点远程仓库页面是否出现了你的项目文件提交历史里的作者是否是你配置的user.name和user.email分支是否已经变成你本地的分支名并且是默认分支。也可以在本机执行git log --oneline查看提交记录。如果远程页面显示的项目文件与你本地一致说明推送成功。4. 第一次握手之后日常推送、代码忽略与排错实战第一次推送完成后这个项目就算从新建推送阶段进入了日常维护阶段。很多人在这一步后又开始踩新的坑我把高频问题集中讲一遍。4.1 第二次推送开始别再用强制推送第一次推送确立共同历史后日常提交与推送的流程是这样的git add . git commit -m 写清楚这次改了什么 git push因为用了-u关联过上游所以直接git push即可。如果这一阶段你习惯每次都加--force我强烈建议改掉。一旦某次你本地落后于远程且有其他人提交了新内容强推会把别人的提交覆盖掉造成的后果比代码冲突严重得多。如果你在一个分支上写完代码要合并到主分支常规做法是git checkout main git pull origin main git merge feature-login git push origin main这里特别强调一点git pull之前先看一下git status确认本地没有未提交的改动。否则一旦合并出现冲突你需要在半成品代码里解冲突处理起来非常痛苦。4.2 .gitignore的两种正确打开方式与修改生效新建项目时建议自己写.gitignore。创建文件的方式很简单touch .gitignore或者用编辑器直接新建。文件放到项目根目录提交到版本库里。规则写法也不复杂玩明白常用的就够忽略单文件直接写文件名比如.DS_Store忽略目录目录名加斜杠比如target/、node_modules/忽略通配*.log表示忽略所有.log后缀的文件取反!important.log表示在忽略的基础上恢复这个文件但是注意取反规则不作用于已忽略目录内的文件细节较多不展开很多新手的困惑在于改了.gitignore之后发现某些文件还是会被提交或者已经提交的文件没有被忽略。这其实是一个很关键的认知问题。.gitignore只对未被Git跟踪untracked的文件生效。如果某个文件已经在版本库里被跟踪了哪怕你事后把它写进.gitignoreGit也不会自动停止跟踪它。正确的处理方式是先把它从Git索引中移除再重新添加git rm -r --cached . git add . git commit -m refresh gitignore git push--cached参数表示只从暂存区/索引中移除文件不会动你磁盘上的实际文件。执行这两步之后那些原本已跟踪的、现在被.gitignore覆盖的文件就真正被忽略了。4.3 只有一个SSH密钥为什么Ping不通平台先看一个非常典型的报错gitgitee.com: Permission denied (publickey).排查链路按下面这个顺序走基本能解决九成问题。先确认密钥是否存在ls -al ~/.ssh没有id_ed25519.pub就重新生成并绑定平台。再确认是否绑定到正确的平台很多人把公钥绑到了GitHub却往Gitee推代码平台当然不认。用ssh -T gitgitee.com可以快速验证。确认公钥粘贴无遗漏复制公钥时注意连结尾的邮箱或者注释一并复制不能少字符。粘贴后提交保存。确认用对仓库地址SSH地址应该是gitgitee.com:用户名/仓库名.git而不是https://gitee.com/...。如果git remote -v显示的远程地址是HTTPS开头说明关联错了用git remote set-url origin改回来。4.4 修改最近一次提交git commit --amend的正确用法有些时候上一条git commit的消息写错了或者漏加了文件。如果这个提交还没推送到远程用git commit --amend非常合适。git add 漏掉的文件 git commit --amend会打开编辑器让你修改提交信息。或者你只想改信息git commit --amend -m 修正后的提交信息这里要特别注意如果该提交已经推送到远程并且这个仓库有其他人也在用不要对这条提交执行--amend。因为amend本质上是创建了一个新的commit来替代旧commit提交哈希会变化远程分支和本地分支的对应关系会被破坏后续一推送就会出现非快速前进的冲突又要被迫走一遍强推流程。4.5 另一种无关历史场景本地拉取远程仓库除了首次推送还有一种常见的无关历史场景你在Gitee上有一个已经初始化过的仓库比如勾选了README但你想把代码拉到一个全新的本地目录里开发而不是反推上去。这种情况直接git clone gitgitee.com:你的用户名/你的仓库名.gitgit clone会完整地把远程历史拉下来并自动建立本地分支不存在无关历史问题。如果你看到一个老项目让你处理两个完全无关的历史时git pull --allow-unrelated-histories才派得上用场比如你想把两个独立初始化的项目合并成一个。这个参数的作用是告诉Git我明确知道这两条历史不相干你帮我强行合并一次。但合并完后两个项目的文件可能交错在一起连根目录结构都需要人工调整不是清理表面冲突就完事的。5. 写在最后的几条真实经验分享几个我自己反复用到的习惯希望能节省你后面踩坑的时间。第一创建仓库时永远选择不初始化任何文件。每勾选一个选项远程就会多一个commit后续处理就多一分麻烦。README也好、LICENSE也好全部等本地代码推上去之后再以文件的形式补加到仓库里再推送一次。这样远程历史干净Git也不会莫名其妙给你生成冲突。第二第一次强推但只限第一次。我这个习惯一开始源于偷懒后来发现它是对的刚创建的空远程仓库没有有价值的commitforce push只是把分支指针从无变成你的提交顶端没有任何信息损失。反而是那些先初始化再手动合并的做法浪费时间且容易在解冲突时误删文件。第三写.gitignore的时间点最好定在项目刚初始化时。很多项目到后面才补.gitignore结果发现target/、node_modules/这些乱七八糟的目录已经被提交到仓库里历史里的每个commit都带着它们仓库体积越来越大。虽然可以用git rm -r --cached清理但历史里残留的内容仍然在。所以项目初始化那一刻就把.gitignore建好是最划算的投资。第四推送报错时先读英文原文再动手。Git的报错信息虽然啰嗦但每一个都带着明确的意图比如Updates were rejected because the remote contains work that you do not have locally就是在告诉你远程有你的本地没有的提交。这时候你该做的不是盲目强推而是先看远程多了什么、有没有你需要保留的东西。等你把Git报错信息读顺了你会发现自己处理Git问题的速度比搜索引擎快得多。
返回列表