本地代码跑通了想放到 GitHub 上留个存档顺便以后换电脑能直接拉下来继续写——这是绝大多数人第一次碰 Git 的真实动机。但真正动手时你会发现卡住人的从来不是git push这一条命令而是它前后的那一堆准备工作哪些文件该进仓库、身份怎么配、远端地址用哪种协议、分支名对不上怎么办、报错信息看半天不知道从哪查起。我见过太多人第一次上传就把node_modules整个推上去仓库体积直接飙到几百兆也见过提交记录里作者名是一串乱码别人想联系你都不知道找谁。这篇东西不讲 Git 的完整理论只解决一件事把本地一个已经存在的项目干净、可维护地传到 GitHub 仓库里。内容覆盖命令行、桌面客户端、网页端三条路径以及上传之后 README、分支、回退这些日常会用到的动作。刚接触 Git 的新手可以照着走已经用过一阵子但总觉得流程含糊的人也能在里面找到几个之前没注意过的细节。1. 上传之前先做减法本地项目里哪些东西不该进仓库1.1 一次典型的翻车node_modules被推上去之后刚学 Git 的人最容易犯的错是站在项目根目录敲一句git add .然后眼睁睁看着几万个文件被加进暂存区。等推送完成仓库首页显示12,438 commits之类的诡异数字或者干脆在推进去的那一刻卡住不动。问题的根源在于对仓库这个概念的理解偏差。Git 仓库不是网盘它记录的是文本的变更历史每一条历史都要在本地和远端各存一份。node_modules里的东西有两个特点一是体积大动辄几百兆二是完全可以从package.json重新装出来。把可再生的产物塞进版本历史里等于把一堆毫无信息量的字节永久写进每一次提交以后想删都删不干净——因为它在历史记录里删除只是一次新的提交旧的那一份还在。正确的判断标准很简单拿到一个目录先问自己两个问题这个东西别人拿到仓库后能不能自动生成能生成的就不进仓库依赖包、编译产物、构建缓存。这个东西是不是我个人的本地环境配置是的话不进仓库IDE 配置、本机路径、个人密钥。按这个标准过一遍Node 项目里该排除的是node_modules/、dist/、build/、*.logPython 项目是__pycache__/、*.pyc、venv/、.pytest_cache/Java 项目是target/、*.class、.gradle/前端项目还要额外加上.next/、.nuxt/、.vite/这类框架缓存目录。macOS 用户记得把.DS_Store加进去Windows 用户记得把Thumbs.db加进去这两个是纯粹的系统垃圾文件出现在别人的 diff 里非常碍眼。1.2.gitignore的写法与取舍逻辑根目录建一个.gitignore文件一行一条规则支持通配符。一个小小的事实是*不跨目录**跨目录而结尾带/表示只匹配目录。很多人的规则写了却不起作用八成是踩了这两个坑。下面这份可以直接拿去改覆盖了大部分常见场景# 依赖与构建产物 node_modules/ dist/ build/ out/ target/ # Python __pycache__/ *.py[cod] venv/ .venv/ *.egg-info/ # 环境变量与密钥重点 .env .env.local *.pem *.key secrets.json # 编辑器与系统 .idea/ .vscode/ .DS_Store Thumbs.db # 日志与临时文件 *.log *.tmp .cache/这里有一条必须单独强调的规则.env和任何存放密钥、令牌、数据库连接串的文件在上传前一定要确认已经被忽略。我见过不止一个项目把云服务的 AccessKey 直接写进配置文件然后推到了公开仓库几小时后收到账单提醒。GitHub 对公开仓库是有爬虫在扫的密钥这种东西一旦推上去就算你下一秒删掉历史记录里依然找得到。处理这类文件的顺序建议是先写.gitignore再执行第一次git add。如果顺序反了文件已经被跟踪之后再往.gitignore里加规则是无效的需要手动执行git rm --cached 文件名把它从跟踪列表里摘出来同时保留本地文件。1.3 目录结构整理与敏感信息的自查正式上传前花十分钟做一次目录巡查收益很高。我习惯按这个清单过一遍有没有写着testcopy备份的临时文件混在源码里有没有本地绝对路径写死在配置文件里比如/Users/xxx/Desktop/project/data有没有注释掉的调试代码和数据抓取的中间产物有没有二进制文件混在源码目录比如压缩包、视频、数据集如果你确实需要放一些示例数据建议新建一个data/sample/之类的小目录只放几十行足以说明格式的样本并在 README 里写清楚完整数据从哪里获取。这个习惯对别人复现你的项目帮助极大也避免了仓库被一个大文件撑爆。2. 把 Git 配好并配好身份这一步偷懒后面全是麻烦2.1 安装与版本验证Windows 上直接去 Git 官网下载安装包一路下一步即可安装过程中建议选 Git from the command line and also from 3rd-party software这样终端里能直接调用。macOS 上最省事的办法是装完 Xcode Command Line Toolsgit命令自带或者用 Homebrew 执行brew install git版本会比系统自带的新一些。Linux 各发行版包管理器直接装这里不展开。装完之后别急着操作项目先在任意目录开一个终端跑两句git --version git config --global --list第一句确认安装成功第二句看当前已有的全局配置。如果第二条报错说找不到配置文件说明你还没配过属于正常。2.2user.name和user.email决定了提交记录长什么样这一步是新手最容易跳过、但后患最长的一步。Git 的每一次提交都会记录作者信息而这个信息来自你本地配置跟你的 GitHub 账号本身没有强制绑定关系。不配的话提交记录里的作者可能显示成unknown或者你操作系统的用户名头像也对不上别人点进去看到一个陌生的名字会怀疑这个仓库是不是你本人的。git config --global user.name 你的名字 git config --global user.email 你的邮箱邮箱这里有个细节值得说如果你在意隐私不想让真实邮箱出现在公开提交记录里可以用 GitHub 提供的专用邮箱。在 GitHub 设置里开启 Keep my email addresses private 之后系统会给你一个类似数字用户名users.noreply.github.com的地址把它填进user.email即可提交依然能被正确归属到你的账号但不会暴露真实邮箱。顺带把默认分支名也一起定了省得后面每次新建仓库都要手动改git config --global init.defaultBranch main现在 GitHub 新建仓库默认用main但很多老版本 Git 本地默认还是master两边不一致的时候推送会提示分支对不上提前统一能少一次困惑。2.3 HTTPS 加令牌与 SSH 密钥两条认证路线怎么选推送代码要证明你是这个仓库的主人GitHub 提供了两种方式。这里的选择会直接影响你之后每天的操作体验值得花两分钟看清楚区别。对比项HTTPS 个人访问令牌SSH 密钥首次配置复杂度低网页生成令牌即可中需生成密钥并上传公钥日常使用体验需要凭据管理器保存令牌配好之后全程免输入令牌有效期可设置到期时间过期需更换长期有效除非主动删除权限粒度可按需勾选仓库读写等范围跟随账号权限适合人群偶尔用一次、临时环境长期开发、多设备使用如果只是想把一个项目传上去以后不常动HTTPS 加令牌足够。操作路径是在 GitHub 网页端进入 Settings找到 Developer settings再进 Personal access tokens生成一个细粒度的令牌勾选目标仓库的 Contents 读写权限。生成后那串字符只会显示一次关掉页面就再也看不到务必当场复制到安全的地方。如果你是长期开发SSH 更省心。生成密钥的命令是ssh-keygen -t ed25519 -C 你的邮箱一路回车会在~/.ssh/下生成id_ed25519私钥绝对不要外传和id_ed25519.pub公钥要上传。用cat ~/.ssh/id_ed25519.pub把内容打出来整段复制到 GitHub 设置里的 SSH and GPG keys 页面新增一个 key 粘贴进去。验证是否配通ssh -T gitgithub.com看到带你用户名的欢迎语就算成功。注意第一次连接会问你是否信任这个主机输入yes回车即可。提示私钥文件等同于你的账号钥匙不要复制到任何聊天窗口、网盘或者项目目录里。如果某个项目目录里出现了id_rsa这类文件立刻从目录里移走并检查.gitignore是否已经排除*.pem和*.key。3. 命令行上传的完整链路从 init 到第一次 push3.1init、add、commit各自在做什么很多人把这三条命令当咒语背下来出了错就不知道从哪下手。理解它们各自负责的环节排查问题时思路会清晰很多。git init做的事情是在当前目录下创建一个隐藏的.git文件夹从此这个目录变成一个被 Git 管理的仓库。它不会动你的任何源文件只是多了一个记录变更的地方。如果你在错误的目录执行了它删掉.git文件夹就能撤销。git add是把工作区的改动放进暂存区。可以理解成你在超市挑东西放进购物车还没结账。git add .是把当前目录下所有未被忽略的改动都放进去git add 文件名只放指定的一个。git commit才是结账把暂存区里的内容打包成一次正式的历史记录并附上你写的说明。提交只影响本地跟远端还没有任何关系。完整的第一次提交流程cd 你的项目目录 git init git add . git status git commit -m chore: 初始化项目中间那句git status建议每次都敲。它会明确告诉你哪些文件被暂存了、哪些没有、哪些被.gitignore忽略了。在执行第一次提交前看一眼这个输出是成本最低的保险。提交信息的写法也有讲究。chore:、feat:、fix:、docs:这类前缀不是硬性规定但用起来能让历史记录一眼看懂。更重要的是别写update修改aaa这种信息量为零的说明三个月后你自己回头看也不知道当时改了什么。3.2 新建远程仓库时的那几个选项勾错了要重来打开 GitHub右上角新建仓库页面上的选项不多但每个都有影响。仓库名字建议全小写加连字符比如my-first-project不要用空格和中文。描述那一栏认真填一句它会显示在仓库页面顶部也是搜索引擎抓取的内容。公开还是私有按需选练习用的项目选私有完全不丢人等成熟了在设置里一键切公开。最关键的是下面那几个初始化选项Add a README file、Add .gitignore、Choose a license。如果你本地已经有项目并且准备用命令行推上去这三个全部不要勾。原因是勾了任何一个远端仓库就不是空的会先有一个提交。而你本地git init之后也有自己的提交历史两者毫无关联直接推送会被拒然后就进入了新手最头疼的合并冲突环节。保持远端为空推送就是一次干净的快进。如果你已经勾了 README 也不慌有两种处理方式后面 3.4 会讲。3.3 关联远端与分支命名对齐仓库建好之后页面上会给出仓库地址形如gitgithub.com:用户名/仓库名.gitSSH或https://github.com/用户名/仓库名.gitHTTPS。用哪条取决于你在 2.3 里选的方案但一旦选定后面就统一用这一种混用容易在凭据上出问题。git remote add origin gitgithub.com:用户名/仓库名.git git branch -M main git push -u origin main三条命令分别做三件事。remote add是给那个长长的地址起个别名叫origin以后推送拉取都用这个别名。branch -M main是把当前分支重命名成main如果你的本地分支本来就叫main这条命令执行了也没坏处。最后push -u里的-u是建立追踪关系好处是以后在这个分支上只敲git push和git pull就够了不用每次带一长串参数。推送过程中如果需要认证HTTPS 方式会弹窗要用户名和令牌把用户名填账号名、密码填那串令牌即可不是账号登录密码。SSH 方式如果 2.3 配好了就是静默通过。3.4push失败的排查顺序推送报错是新手最慌的时刻但其实大部分错误就那么几种按顺序查很快能定位。第一种remote origin already exists。说明这个目录之前已经关联过远端地址了。想换地址的话用git remote set-url origin 新地址想删掉重来用git remote remove origin。用git remote -v可以随时查看当前关联的是什么。第二种failed to push some refs或提示non-fast-forward。这是最常见的表示远端有你本地没有的提交——通常是因为建仓库时勾了 README或者你之前在网页上直接改过文件。处理方式git pull --rebase origin main git push origin main--rebase的作用是把你本地的提交垫到远端最新提交的后面历史记录会是一条直线比默认的合并提交干净。如果拉取过程中出现了冲突提示说明同一个文件的同一处被两边都改了需要手动打开文件找到、、三行标记决定保留哪部分删掉标记行然后git add 该文件再git rebase --continue。第三种Support for password authentication was removed。这是用账号密码直接推送导致的说明你没用令牌。回到 2.3 生成一个令牌然后更新远端地址或者在弹出的凭据窗口里换成令牌。第四种403 Forbidden或permission denied。这两种含义不同。403 一般是令牌权限范围没勾对或者你尝试推送到一个不属于你的仓库permission denied (publickey)是 SSH 公钥没配对重新检查公钥是否完整粘贴、是不是粘错了换行然后用ssh -T gitgithub.com验证。第五种文件过大。提示里会明确写出超过 100 MB 的文件名。GitHub 单文件硬上限是 100 MB超过就必须处理。小文件用git lfs跟踪大文件比如数据集、视频建议干脆不要进仓库放到别的地方并在 README 里给出获取方式。如果这个文件已经在历史提交里了只是删掉还不够需要用git filter-repo之类的工具把历史重写一遍这条后面的章节会展开。一条经验报错信息里的英文不要跳过逐词读一遍。Git 的报错其实写得很直接它通常会告诉你是什么问题、建议用什么命令比翻论坛快得多。4. 不想敲命令桌面客户端与网页端拖拽上传的边界4.1 GitHub Desktop 的适用场景与它替你做了什么不是所有人都愿意跟命令行打交道GitHub Desktop 就是为这部分需求准备的。它的优势是把暂存—提交—推送三个动作变成了界面上的三次点击并且会用颜色把改动标出来对建立直观感受很有帮助。使用流程大致是安装并登录账号选择 Add local repository指向你的项目目录。如果这个目录还不是 Git 仓库软件会提示你创建一个这一步相当于替你执行了git init。之后界面左侧会列出所有改动过的文件勾选你想提交的在左下角填一句提交说明点 Commit再点顶部的 Push origin。它替你处理了几件容易出错的事自动写好.gitignore模板新建仓库时可以选择语言模板、自动处理凭据保存、推送前自动检查远端是否有新提交。但它也有两个要注意的地方。一是它只会跟踪你允许的改动范围大文件它不拦。一个几百兆的文件照样能被提交然后推送时卡住。二是它默认的合并策略是 merge 而不是 rebase如果多人协作历史记录会出现不少交叉线。个人项目无所谓团队项目里习惯命令行的人可能会觉得历史不够清爽。4.2 网页上传的两个硬限制如果只是想把几个文件丢上去网页端最省事在仓库页面点 Add file选 Upload files把文件拖进去填一句提交说明点提交就行。上传整个文件夹也是支持的直接把文件夹拖到虚线框里浏览器会自动展开里面的文件。但有两个限制必须先知道否则很容易上传到一半失败限制项具体数值超限表现单次上传文件数100 个提示文件数过多需要分批单文件大小25 MB提示文件过大建议用命令行推荐仓库总体积1 GB 以内超过会收到提醒影响克隆速度所以网页端适合的是那种几十个文件以内、单个文件都不大的小项目比如几个脚本、一份配置、一些文档。真要传一个完整的开发项目还是老老实实走命令行尤其是在已经装了依赖的情况下文件数量动辄上万。4.3 上传单个文件夹与空目录的坑有人问过怎么只上传一个文件夹其实做法很简单在仓库页面点 Add file 再选 Create new file在文件名输入框里用/分隔路径比如输入docs/guide/intro.mdGitHub 会自动把docs和guide两层目录建出来然后你在内容区写点东西保存即可。虽然有点绕但这是网页端唯一能创建嵌套目录的方式。另一个坑是空目录不会被 Git 跟踪。Git 的设计里只记录文件不记录目录所以一个里面什么都没有的文件夹git add之后在提交里根本不存在。如果你确实需要保留某个目录结构比如项目运行需要logs/目录存在习惯做法是在里面放一个名为.gitkeep的空文件。这个名字没有特殊含义只是社区约定俗成放什么名字其实都行但.gitkeep已经成了大家都能看懂的信号。对应的.gitignore里通常会写logs/* !logs/.gitkeep意思是忽略logs下的所有内容但那个占位文件除外。5. 项目上去之后README、分支与后续日常维护5.1 README 决定别人点不点进来仓库页面上README 的内容会直接渲染在文件列表下方也就是说它是访客第一眼看到的东西。一个只有代码没有 README 的仓库哪怕写得再好被点开、被使用的概率都会低很多。反过来说一份结构清楚的 README 能让同一个项目的观感提升一个档次。我习惯按这个顺序写长度控制在能一屏读完一句话说明这个项目解决什么问题不要写这是一个基于 XX 的项目这种废话直接写它能干什么一张效果截图或动图如果有界面的话这一条的价值超过一千字描述快速开始从安装依赖到跑起来的最短路径命令要能直接复制粘贴目录结构说明只列主要目录每个一行注释依赖与环境要求语言版本、需要的外部服务许可证说明快速开始这一段特别值得打磨。判断标准是一个没接触过项目的人照着这一段复制粘贴能不能在五分钟内跑起来。常见的问题是漏了环境变量的配置说明或者默认别人机器上已经装了某个全局工具。把需要手动创建的配置文件写清楚比如把.env.example复制为.env并填入你的数据库地址这一句能帮别人省掉半小时排查。5.2 分支、标签与版本节点的管理个人项目一直在main上直接提交没什么问题但当你开始有新功能写到一半、不想影响当前可用版本的需求时分支就有用了。git checkout -b feature/export-csv git push -u origin feature/export-csv分支名建议用feature/、fix/、docs/这类前缀加简短描述一眼能看出这条分支要干什么。功能做完之后在网页端发起合并请求自己 review 一遍再合并比直接往主干推更稳当。标签用于标记版本节点。当你觉得某个提交的状态是一个可以发布的版本时git tag -a v1.0.0 -m 首个可用版本 git push origin v1.0.0之后别人就能在 Releases 页面直接下载这个版本的打包文件不用自己克隆再切分支。对于工具类项目来说这个动作能明显降低别人的使用门槛。5.3 改错了怎么退几种撤销方式的区别上传之后发现提交信息写错了、文件提交多了是很正常的。关键是选对撤销方式别一上来就用最激进的手段。场景推荐命令说明提交信息写错了还没推送git commit --amend -m 新信息直接改掉上一条提交历史不变多多提交了一个文件还没推送git reset --soft HEAD~1撤销提交但保留改动重新组织后再提交某次提交引入了问题已经推送git revert 提交哈希生成一条反向提交历史可追溯最安全只想撤销某个文件的改动git checkout -- 文件名恢复到最近一次提交的状态改动会丢失git reset --hard会连带丢掉工作区的改动用之前务必确认没有还没保存的内容。已经推送到远端的提交尽量用revert而不是reset加强制推送因为后者会改写公共历史协作者拉取时会遇到麻烦。还有一个隐蔽的场景误推了敏感文件。仅仅在最新提交里删掉是不够的历史记录里依然存在别人克隆下来照样能看到。这种情况需要在所有提交里重写历史用git filter-repo --path 敏感文件路径 --invert-paths这类命令处理处理完强制推送并且立刻把那个密钥作废重新生成。删文件只是止损作废密钥才是真正的修复。6. 高频报错对照与我的排查习惯6.1 报错对照表把前面散落的报错集中放一张表遇到问题时可以先在这里定位再回头翻对应章节。报错关键词真实原因处理动作not a git repository当前目录没执行过 initgit init或切换到正确目录remote origin already exists重复添加远端git remote set-url origin 新地址non-fast-forward远端有本地没有的提交git pull --rebase origin main后再推refusing to merge unrelated histories两边历史完全独立确认内容后加--allow-unrelated-histories拉取Support for password authentication was removed用了账号密码而非令牌生成个人访问令牌替换403 Forbidden令牌权限不足或无仓库权限重新生成并勾选目标仓库写权限permission denied (publickey)SSH 公钥未配置或粘贴不全重新上传公钥ssh -T验证file is 105.00 MB; exceeds limit单文件超过 100 MB移出仓库或用 LFS 跟踪src refspec main does not match any本地还没有提交或分支名不对先确认git log有记录检查分支名Filename too long文件路径超过系统限制Windows 上执行git config --global core.longpaths true6.2 我自己的排查顺序习惯遇到推送失败我基本按这个固定顺序走一遍通常三五分钟能定位git status先看本地状态。有没有还没提交的改动有没有处于 rebase 或 merge 的中间状态大部分推不上去其实是本地根本没提交。git remote -v确认远端地址是不是指向了正确的仓库。我踩过一次把公司项目的地址配到了个人练习目录里。git log --oneline -5看本地最近几条提交确认分支名和提交数量符合预期。git pull --rebase先同步远端八成的问题在这一步就暴露出来了。再推送如果这一步还报错把完整报错信息逐词读一遍通常答案就在里面。这个顺序的价值在于从本地往远端查而不是一上来就怀疑网络和账号。实际经验是十个推不上去里有七个是本地状态的问题两个是分支不一致只有一个是权限或网络相关。6.3 几个容易忽略的细节换行符问题。Windows 和 macOS/Linux 的换行符不一样跨平台协作时会出现明明没改diff 里整个文件都变了的情况。统一处理的办法是在项目根目录加一个.gitattributes文件* textauto eollf *.bat text eolcrlf意思是对所有文本文件在仓库里统一存为 LF但.bat脚本在检出时转成 CRLF。加了这个文件之后再提交一次可以让 Git 重新规范化已有的文件。大小写敏感问题。macOS 默认文件系统不区分大小写Windows 也不区分但 Git 是区分的。如果你把Utils.js改名为utils.js在本地可能看不出变化但推送后仓库里会出现两个文件。解决办法是用显式重命名git mv Utils.js temp.js git mv temp.js utils.js克隆到已有目录。如果你本地已经有一份代码想跟远端仓库建立联系不用重新克隆直接在那个目录里git init、git remote add origin 地址、git fetch、git checkout main也行。但这种方式容易和已有文件冲突更省事的做法还是先克隆到临时目录把需要的文件复制过去再正常提交。.git目录不要手动动。里面装的是整个版本历史删掉等于仓库归零。有人为了清掉历史直接删.git再重新 init这样做确实简单粗暴但也会丢掉所有提交记录。如果那个仓库是公开的、别人已经克隆过重写历史后他们的本地版本就对不上了需要提前沟通。推送之后的等待。有时候网页上要刷新一下才看到更新尤其是刚创建仓库的第一次推送页面缓存可能还没反应过来。别急着怀疑推送失败先看命令行的输出里有没有 branch main set up to track 之类的成功提示。说到底把本地项目传上 GitHub 这件事命令本身只有五六条真正花时间的是前期的整理和后期遇到问题时知道往哪查。我第一次上传时把整个虚拟环境推了上去仓库里多了两万多个文件后来花了一个多小时重写历史才清理干净。那次之后我就养成了一个习惯任何项目在git init之前先建.gitignore先把.env检查一遍再看一眼git status的输出。这三步加起来不到两分钟但能省掉后面一大堆麻烦事。