1. 先弄明白GitHub和Git,再开始动手
1.1 一句话讲清Git和GitHub的关系
很多新手会把Git和GitHub当成一回事,其实它们是两样完全不同的东西。Git是一个在你电脑本地运行的版本管理工具,负责记录代码每一次的改动,相当于一个细到文件行级别的"后悔药系统";GitHub则是一个基于Git的云端代码托管平台,把你的仓库放到网上,方便备份、展示和多人协作。打个比方,Git是负责记账的"档案管理员",GitHub是存放档案的"云端档案库",管理员把账记好后,再把账本同步到云端给别人看。
对刚接触GitHub的人来说,你需要掌握的核心工作流其实就三条。第一,在本地写代码,用Git随时记录每次修改;第二,把本地记录的一个版本推送到GitHub,这个动作叫push;第三,把GitHub上的最新版本拉回本地,这个动作叫pull。一旦把这三条线串起来,日常开发的基本循环就成立了,你就能理解为什么程序员总说"代码放在GitHub上",而不只是放在自己电脑上。
这篇文章会围绕标题里最核心的两件事展开:怎么把一个本地项目上传到GitHub,以及改完代码之后怎么把修改内容同步上去。我不打算讲太多理论,重点放在可直接照抄的操作流程和真实踩过的坑上。已经会pull / push的老手可以跳过前面两章,直接从遇到的报错部分开始看,后面几章有不少排查经验值得扫一眼。
| 名词 | 作用 | 通俗解释 |
|---|---|---|
| Git | 本地版本管理工具 | 记录文件每次变化的"账本" |
| GitHub | 云端代码托管平台 | 把本地仓库同步到网上的"档案库" |
| Repository(仓库) | 项目存放位置 | 一个项目对应一个仓库 |
| Commit(提交) | 保存一个版本 | 给当前文件变化拍一张快照 |
| Push(推送) | 本地到远程 | 把本地快照传到GitHub |
| Pull(拉取) | 远程到本地 | 把远程更新同步回本地 |
| Clone(克隆) | 远程到本地完整复制 | 把别人仓库完整下载到本地 |
1.2 动手之前,先把三件事准备好
注册GitHub账号。打开GitHub官网,点击Sign up,按流程填邮箱、密码、用户名。用户名不是邮箱前缀,而是一个唯一的公开ID,它会在仓库地址里出现,别人搜索你、查看你提交的代码都靠它,所以取一个自己长期用且不容易撞车的名字。注册完会发一封验证邮件到你的邮箱,点一下激活链接,账号才算正式开通。
安装Git。这是很多人忽略的一步。GitHub网页只能让你上传单个文件、查看代码,真正的版本管理能力都在本地Git命令里。Windows用户建议直接去Git官网下载安装包,一路默认安装即可,安装完成后在"开始"菜单找"Git Bash",打开后输入git --version,能看到版本号就说明装好了。macOS用户如果装了Homebrew,一条命令搞定:brew install git。Linux用户对应使用apt install git或yum install git。装完同样用版本命令验证。
提示:Windows安装Git过程中有一个"Adjusting your PATH"选项,默认选"Git from the command line and also from 3rd-party software"就行,这样能在CMD和PowerShell里直接使用git命令。
准备一个本地测试项目。不用折腾什么大项目,就在桌面上新建一个文件夹,命名为my-first-project,里面放一个README.md文本文件,随便写两行内容。后面所有操作都在这个文件夹里练,跑通流程之后,再拿自己的真实项目来操作就不慌了。
2. 上传首个本地项目:从新建仓库到第一次Push
2.1 在GitHub上新建一个干净的远程仓库
登录GitHub之后,页面右上角有一个"+"号,点开后选择New repository,进入新建仓库页面。这里的配置项我逐个说,因为选错会影响后面推送的顺畅度。
Repository name是仓库名,建议和本地文件夹名一致,比如my-first-project;Description是仓库简介,先填不填无所谓;Public和Private是可见范围,Public代表公开仓库,任何人都能看到,适合开源项目和作品展示,Private是私有仓库,只有你主动添加的协作者能看到,新手练习阶段强烈建议先用Private,避免把还没处理好的配置信息暴露出去。
下面有一个选项叫"Initialize this repository with a README",这里一定不要勾选。因为我们要练习的是"从本地把已有项目推上去"这条路,如果勾选了,GitHub会自动生成一次初始提交,本地仓库和远程仓库的历史就对不上了,等下推送时会多出合并的麻烦。保持仓库完全是空的,直接点击Create repository。
创建完成后,页面会跳转到仓库主页,并显示一段初始化指引,里面给出了远程仓库地址。地址通常有两种形式:
https://github.com/你的用户名/my-first-project.git git@github.com:你的用户名/my-first-project.git前者是HTTPS协议,第一次push会让你输入账号信息,简单直接;后者是SSH协议,配置一次之后免密推送,体验更好。第一次上手先使用HTTPS方式,跑通了再换成SSH也不迟。
2.2 在本地项目文件夹里初始化Git
进入项目文件夹,打开终端。Windows用户在文件夹地址栏输入cmd回车,Mac用户可以直接在访达里打开"终端",再cd到对应目录,最稳妥的方式是在终端中拖动文件夹图标定位路径。
cd ~/Desktop/my-first-project git init执行git init后,终端会输出Initialized empty Git repository in ...这样一行提示。这条命令会在当前目录生成一个隐藏的.git文件夹,里面放着Git所需的全部元数据,你的项目从此就是一个合法的Git仓库了。不要手动去改动.git里的内容,它不需要你操心。
接下来设置Git身份。这里设置的不是GitHub账号,而是"提交署名",每一次commit都会记录这个信息:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"--global意味着这台电脑上所有仓库都会默认采用这个身份,很适合个人电脑使用。如果某个项目想用不同的身份,可以去掉--global,在项目目录里单独设置,优先级会高于全局配置。
然后用两条命令完成第一次提交:
git add README.md git commit -m "第一次提交:添加README"你会发现Git命令总是"先add再commit"的固定套路。这里add是把文件放入暂存区,commit才是真正生成一个版本记录。-m参数是必须的,它后面跟的字符串就是本次提交的说明。如果不写-m,Git会进入一个系统默认文本编辑器等你输入说明,新手在vim界面里往往不知道怎么退出,非常影响体验,所以请一定记得加-m。
执行完commit后,用git status看一眼当前状态,如果显示nothing to commit, working tree clean,说明本地仓库已经记录好当前版本,可以推送了。
2.3 关联远程仓库,完成第一次推送
现在把本地仓库和GitHub上的远程仓库连起来。
git remote add origin https://github.com/你的用户名/my-first-project.git这里的origin是Git给远程仓库起的默认别名,用来代替一长串地址。你可以理解成给朋友手机号存了备注名,以后打电话不用拨号,喊一声"origin"就拨出去了。
接下来把本地当前分支的名字改成main。这里有个背景:GitHub官方现在默认主分支叫main,而早期Git初始化时默认分支叫master,为了两边的分支名保持一致,需要执行一条改名命令:
git branch -M main然后正式推送:
git push -u origin main-u的全称是--set-upstream,它的作用是让本地main分支和远程main分支建立跟踪关系。有了这个关系,以后在这个分支上直接敲git push就能推送,不需要再重复写origin main。
第一次执行push时,终端会弹出一个登录窗口,要求输入GitHub的用户名和密码。这里有一个新手必踩的坑:密码栏里输入的并不是你登录GitHub的账号密码,而是Personal Access Token,也就是个人访问令牌。GitHub早已不再接受账号密码形式的git操作认证,如果你老老实实输入登录密码,一定会得到Authentication failed的报错。具体怎么生成这个Token,我在第五章节里详细讲。
当你正确输入用户名和Token之后,终端会输出一堆打包信息,最后出现main -> main之类的字样,就说明push成功了。回到GitHub仓库页面刷新一下,README.md已经躺在文件列表里。这一步是整个GitHub学习曲线里最陡的一关,跑通之后,一切都会变得顺理成章。
3. 修改本地项目后的同步流程:add、commit、push三件套
3.1 理解工作区、暂存区、版本库的关系
项目首次上传到GitHub后,你日常面对最多的场景就是:改了一个或多个文件,想把这些改动同步到GitHub。很多人第一次接触时直接问"我怎么更新GitHub上的文件",其实底层就是三个命令,合起来叫"提交三件套":
git add . git commit -m "更新说明" git push虽然看起来简单,但盲目复制命令会让你在出现问题时完全摸不着头脑。这三个命令背后对应着Git的三个核心区域,我建议你花五分钟理解一下。
第一个是工作区(Working Directory),也就是你在VS Code、记事本里打开的文件本身,你改哪一行代码,改动都先发生在这里。第二个是暂存区(Staging Area),用git add把工作区里想提交的文件放进去,相当于去超市购物时推的购物车,你先选择把哪些商品放进去,最后再结账。第三个是版本库(Repository),用git commit把暂存区里的内容正式结算成一个版本,得到一个不可变的历史快照。
拿一个具体例子串一遍。假设你的README.md里本来写着"这是一个测试项目",你改成"这是一个GitHub练习项目",保存文件后,改动只存在于工作区。如果你直接执行git push,Git会告诉你当前没有可推送的内容,因为改动还没被记录。正确的动作是先git add README.md,把改动放进暂存区,再git commit -m "更新项目介绍",生成一个新版本,最后git push推送到GitHub。
如果改动的文件很多,git add .会把工作区里所有被修改、被删除以及新增的文件全部放进暂存区,效率确实高,副作用是按文件筛选的精细度没了。所以我个人的经验是:如果改动只涉及两三个已知文件,就精确添加;如果确实是一次大型改动、很多文件一起改完,再用git add .也不迟。
3.2 怎么用git status和git diff确认改动
推送之前强烈建议先看一眼当前仓库状态,这个操作能避免大量手滑事故:
git status输出的内容会告诉你三件事:当前在哪个分支、哪些文件有改动、这些改动是被暂存还是没有暂存。比如:
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: README.md看到这一行,说明README.md有改动但还没有add。如果想更进一步确认具体改了什么内容,执行:
git diff README.mdgit diff会以增量对比的方式显示每一行的变动,-开头的是原来的内容,+开头的是当前的内容。看一遍diff再提交,基本能避免"我以为我改的是这个,结果提交了另一个"的低级错误。
3.3 提交信息到底怎么写才不后悔
git commit -m "更新"这种写法我见过太多了。一天提交五次,五次都是"更新",等过一个月想回滚到某次改动,翻提交历史时你会想抽当时的自己。
提交信息是写给"未来的自己"和"协作队友"看的,最基本的要求是说明这次改动做了什么、为什么要做。业界有一个朴素的约定,用动词开头,常见的有这样几类:
feat: 新增了用户登录功能fix: 修复登录按钮点击无响应的问题docs: 更新README文档style: 调整代码缩进和格式refactor: 重构用户模块的代码结构
不一定非要按Conventional Commits的规范来,但我强烈建议至少做到"动词 + 具体对象 + 问题描述"的格式。比如:
git commit -m "fix: 修复首页在移动端样式错乱的问题"多看几眼GitHub上知名开源项目的提交记录,你会发现大家的写法都高度类似。这个习惯从第一天就培养,后面受益无穷。
3.4 推送之前先拉取:避免冲突的关键习惯
如果你的仓库只有一台电脑在用,push基本不会出问题。但只要仓库被另一台电脑、另一个协作者,或者你自己在GitHub网页端改过文件,本地仓库就可能落后于远程仓库。这时候直接push,Git会拒绝推送,并提示non-fast-forward之类的错误。
正确的习惯是:推送之前先同步远程的最新改动。
git pull origin maingit pull本质上做了两步:先从远程下载最新提交到本地,也就是fetch;再把下载下来的内容合并到当前分支,也就是merge。如果远程有改动而你没先pull,push就会被拒绝;pull之后再把本地改动push上去,流程就顺了。
如果没有冲突,pull会自动完成合并,你继续push即可。如果远程和本地改动的是同一个文件的同一行,Git无法自动判断谁对,这时候会出现冲突(CONFLICT),并在冲突文件里用<<<<<<<、=======、>>>>>>>标记出两边的内容。打开文件,手动保留需要的部分,删掉冲突标记,然后重新add、commit、push,冲突就解除了。
第一次遇到冲突确实会紧张,但它本质是在帮你做"版本裁决":你和我改的是同一个地方,到底听谁的,你自己看清楚决定。所以我会建议形成这样一个操作顺序:改代码之前先pull,改完之后commit,再push。这样能最大程度减少冲突的发生。
4. 上传文件夹、大文件与忽略规则
4.1 整个项目文件夹怎么一次传上去
如果你之前没有为项目做过Git初始化,现在有一个完整的项目文件夹,里面有多个子目录、多个文件,想一次性上传到GitHub,流程跟单文件一致,区别只在git add的范围。
cd 项目根目录 git init git add . git commit -m "上传整个项目" git remote add origin https://github.com/你的用户名/仓库名.git git branch -M main git push -u origin main执行完这串命令,Git会递归扫描整个文件夹,把所有文件和文件夹的相对路径一并记录并推送。这里有一个很容易踩的点:本地新建的空文件夹通过git add .并不会被上传到GitHub。原因是Git只跟踪"文件",不跟踪"目录",它要求目录里至少有一个文件才存在。想让空文件夹出现在远程仓库里,通用的土办法是在里面放一个占位文件,命名通常叫.gitkeep,内容留空,纯粹用来"占坑"。这不是GitHub的限制,而是Git本身的设计,明白原理之后就不会再困惑。
如果你只是临时想传一个文件夹,不想初始化整个项目的Git历史,GitHub网页端直接支持拖拽上传:仓库页面进入目标目录,点击Add file下拉菜单里的Upload files,然后把整个文件夹拖进浏览器窗口就行。网页端会自动创建文件夹结构。但这个方案有两个明显缺点:一是无法保留版本记录,二是大文件列表容易卡住。适合"传一次就完事"的场景,正式开发不建议用。
4.2 .gitignore:把不需要提交的文件挡在门外
项目里不是所有文件都应该进Git。最常见的例子是node_modules,一个JavaScript项目装完依赖后,这个目录体积轻松超过几百MB,里面全是可重新生成的第三方代码,完全没有入库的必要。如果被误传上去,对仓库是灾难性的。
解决方式是项目根目录放一个.gitignore文件,在里面写明哪些路径、哪些规则需要忽略。一个典型的例子:
node_modules/ dist/ build/ .env .env.local *.log .DS_Store Thumbs.db规则很直白:node_modules/表示忽略这个目录以及目录下所有内容,.env表示忽略这个文件,*.log表示忽略所有.log后缀文件,.DS_Store是macOS系统自动生成的元数据文件,应该忽略。
把.gitignore写好之后,再执行git add .时,这些文件会被自动跳过。刚开始拿真实项目上传时,建议先写.gitignore再add,不要等到git status里出现一大堆没打算传的文件才想起处理。
4.3 大文件和二进制资源,别硬塞进仓库
GitHub对上传文件大小有硬性限制:单文件超过50MB会出现警告,超过100MB时push会被直接拒绝。如果你的真实项目里有训练好的模型权重、安装包、视频素材这类大文件,硬往GitHub塞不仅会失败,就算侥幸传上去,每次clone也会把仓库拖到非常慢。
处理大文件的思路通常有三个。第一,小体积素材(比如图标、图片)直接入库没问题;第二,大体积程序文件放网盘、对象存储,通过URL访问;第三,如果确实需要版本管理大文件,Git LFS(Large File Storage)可以帮忙,它会把大文件替换成轻量指针文件,实际内容存到LFS服务器上。但LFS免费额度大约只有1GB,配置也有所门槛,小项目直接用网盘方案更省心。
push报错的时候,通常会看到类似this exceeds GitHub's file size limit of 100 MB的提示。处理顺序是:先用git log找到包含大文件的提交,再用git rm --cached把它从Git目录中移除,然后把它加进.gitignore,最后重新commit和push。如果这个文件已经被历史提交记录了多次,光删当前版本不够,历史里依然有它,就需要重写历史。对新手来说,与其折腾filter-branch这种高风险操作,不如把本地剩余文件整理干净,重新建一个远程仓库传一次,反而更省时间。
5. 初学GitHub最容易踩的坑和排查思路
5.1 认证失败:现在必须用Token,不是登录密码
第一次push就遇到认证失败,可以说是GitHub新手最普遍的第一课。报错信息一般是这样的:
fatal: Authentication failed for 'https://github.com/你的用户名/my-first-project.git'原因大概率很简单:你在密码栏里输入的是GitHub登录密码。但GitHub从很早开始就不再接受账号密码形式的git操作,现在要求使用Personal Access Token,也就是个人访问令牌。
生成Token的完整路径是:GitHub页面右上角点头像 → Settings → 左侧最下方Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic)。做一次会看到一堆字段:
- Note:给这个Token起个名字,比如"我的笔记本";
- Expiration:有效期设置,建议90天以内,长令牌政策在收紧,短一点的更安全;
- Select scopes:权限范围,个人项目勾选repo(完整仓库读写权限)即可,不需要额外选择admin之类的高权限。
生成之后,页面上会出现一串以ghp_开头的字符串,请立刻复制保存。这个Token只显示一次,关掉页面后就再也看不到了,忘记只能重新生成。
之后push时提示输入密码,粘贴这串Token进去(终端里输入内容不会显示任何字符,这是正常的,不代表死机),回车就能完成认证。如果以后每天都要push,每次都输入Token确实烦人,那就用上一章提到的SSH配置解决。
5.2 push被拒绝:non-fast-forward该怎么处理
! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'https://github.com/...' hint: Updates were rejected because the tip of your current branch is behind its remote counterpart.看到这串报错,含义是"你本地的版本落后于远程版本",Git拒绝用落后的版本覆盖远端。新手第一反应经常是"那我强制覆盖吧",这里我必须提醒你:git push -f这种强制推送能不用就不用,尤其是多人协作或公开仓库,覆盖意味着别人辛苦推上去的代码会消失。
正确的解决顺序:
git pull origin main git push先拉取远程更新,合并到本地,再推送。如果拉取过程中报了冲突,进入3.3提到的冲突解决流程,手动处理后再add、commit、push。还有一个进阶选项是git pull --rebase origin main,rebase会把本地提交"垫"到远程最新提交之后,提交历史更干净,但依然可能弹冲突,处理方式相同。新手从普通pull开始就够了。
5.3 误提交、误删除,怎么优雅回滚
开发过程中难免手滑,把不该提交的文件放进去了,或者提交信息写错了。Git给了不少"后悔药",但要分清场景,用错了更麻烦。
如果只是想从Git跟踪中移除一个文件,但保留本地文件,用:
git rm --cached 文件名比如误把.env提交上去了,这条命令能把它移出Git跟踪,再配合.gitignore防止之后再被add,非常实用。
如果想撤销最近一次commit,但保留改动内容,用:
git reset --soft HEAD~1HEAD~1指的是上一个版本,--soft表示只回退commit记录,暂存区和工作区都不动。适合"提交信息写错了"或者"提交完之后发现还忘加一个文件"的场景。
如果想连暂存区也清空,但不想丢弃工作区修改,用:
git reset --mixed HEAD~1这是不带参数的默认行为。如果连工作区改动也想彻底丢弃,才用:
git reset --hard HEAD~1请特别注意:--hard会丢弃最后一次提交里的所有文件改动,是不可恢复的,执行前一定要确认本地内容不需要了。我见过不止一个同事在没备份的情况下敲了--hard,然后对着消失的代码怀疑人生。
如果已经push到远程了才发现提交有问题,更推荐的做法是在GitHub网页端操作Revert:进入Commits列表,找到问题提交,点击Revert按钮,GitHub会自动生成一条反向提交,既保留历史,又撤销改动。这种方式对新手最友好,因为不会动你的本地历史。
5.4 新手问题速查表
| 错误现象 | 常见原因 | 解决办法 |
|---|---|---|
| Authentication failed | 用了登录密码而非Token | 生成PAT,作为密码输入 |
| non-fast-forward rejected | 本地版本落后于远程 | git pull origin main,再push |
| File size limit 100 MB | 上传了超过100MB的文件 | 移除大文件,改用LFS/网盘 |
| 文件没有出现在push里 | .gitignore匹配到了目标 | 检查.gitignore规则并调整 |
| 空文件夹没上传成功 | Git不跟踪空目录 | 添加占位文件(如.gitkeep) |
| LF will be replaced by CRLF | Windows换行符处理警告 | 正常提示,无需处理 |
| Permission denied (publickey) | SSH密钥未配置正确 | 检查公钥是否已添加到GitHub |
| 忘了当前在哪个分支 | 分支切换后没有查看 | git branch -v 查看当前分支 |
这张表基本覆盖了头一个月能用到的坑。遇到报错不要慌,先把这个报错信息复制到搜索引擎里搜,GitHub相关的报错大概率已经有人踩过千百次了。
6. 三个提升推送效率的实用技巧
6.1 配置SSH,告别每次输Token
HTTPS方式第一次用起来方便,但天天push的话,每次输入用户名和Token真的很烦。SSH方式一次性配置,终身免密,强烈建议早点换。整个流程四步走。
第一步,生成SSH密钥对。在终端执行:
ssh-keygen -t ed25519 -C "你的邮箱"一路回车即可,如果需要设置密码口令就输入一个。执行完会在~/.ssh目录下生成两个文件:id_ed25519是私钥,绝不能给别人看;id_ed25519.pub是公钥,可以放心公开。
第二步,查看公钥内容并复制。执行:
cat ~/.ssh/id_ed25519.pub以ssh-ed25519开头的一长串字符串就是公钥,把它整段复制下来。
第三步,在GitHub上添加公钥。进入Settings → SSH and GPG keys → New SSH key,Title随便填,粘贴公钥,保存。
第四步,把远程仓库地址换成SSH形式。在项目目录执行:
git remote set-url origin git@github.com:你的用户名/仓库名.git之后执行git push,第一次会提示是否确认连接,输入yes回车,之后就全程无感推送了。这一步配好之后,配合VS Code等编辑器自带的Git功能,体验可以直接对标商用工具的丝滑。
6.2 写一个一键推送的小脚本
个人项目改动频繁,每次重复敲add、commit、push确实会让人烦。我给自己写了一个小脚本,放在项目根目录,一行参数就搞定提交说明:
#!/bin/bash # 用法: ./push.sh "提交说明" cd "$(dirname "$0")" git pull origin main git add . git commit -m "$1" git push保存为push.sh之后,执行chmod +x push.sh赋予执行权限。以后改完代码,只需要:
./push.sh "feat: 新增用户列表接口"脚本会自动先pull拉取远程更新,再提交和推送。我在脚本里特意把pull放在最前面,是为了减少push被拒绝的可能性。个人练手、自用项目用这个没问题,但在多人协作的项目里还是建议手动确认git add了哪些文件,脚本一键全量提交容易把临时文件、调试代码一起推上去,得不偿失。
6.3 图形化工具的选择建议
如果命令行一开始让你觉得门槛偏高,完全可以用图形化工具过渡。GitHub官方推出的GitHub Desktop是门槛最低的选择,界面简洁,Commit、Push、Pull都对应着明确的大按钮,文件变更列表也很直观,适合第一次建立"提交、推送、拉取"的肌肉记忆。VS Code内嵌的源代码管理面板也非常实用,左侧可以看到每个文件的修改状态、diff对比,提交和推送都在同一个面板里,几乎所有用VS Code写代码的人都会顺手用它处理Git操作。SourceTree这类老牌工具功能更强,分支图可视化更清晰,但界面信息密度较高,新手一上来容易被绕晕。
我的建议是:前期用GUI工具能让你快速跑通流程,但git status、git log、git diff这几个查询命令还是值得花点时间学会。排查问题、写脚本、在服务器上操作仓库时,命令行比任何GUI都直接。最终你会发现它通用、快速、不受图形环境限制,这也是很多老手最终还是回到终端的原因。
最后分享一个小习惯:每开始一个新项目,第一时间创建一个.gitignore,把日常依赖目录、构建产物、环境配置全部排除掉,不要等传了一半才想起处理。还有,尽量保持一次提交只做一件事,提交信息里说清楚"做了什么、为什么做"。我见过太多人因为提交信息全是"update"而无法定位历史版本,最后只能逐条翻diff硬查。GitHub的学习曲线说陡也陡,说平也平,跑通你的第一次push,后面就顺了。