1. 为什么说GitHub仓库操作的本质是Git命令
很多人在用GitHub的时候,习惯打开网页,从网页上下载代码、点几个按钮新建仓库、手动上传文件。说实话,网页端做这些事确实够用,但一旦你开始接触真正的项目,尤其是需要频繁更新代码或者跟别人协作的时候,纯网页操作会让人抓狂。我经常在技术社区里看到有人问"怎么在GitHub上删除一个文件夹""怎么回滚一次错误的提交",这些操作用网页端做起来特别别扭,但切换到命令行,几行命令就能解决。
这里要先理清一个底层逻辑:GitHub本质上只是Git的托管平台,它自己并不产生版本控制功能。你在GitHub上看到的所有仓库,背后都是基于Git这个分布式版本控制系统。所以"GitHub仓库常用命令"其实就是在讲"Git操作GitHub仓库的命令"。掌握了这些命令,你不仅能在GitHub上顺畅管理代码,任何基于Git的代码托管平台——GitLab、Gitee、Bitbucket——都能无缝迁移。
为什么必须用命令行?因为命令行能让你直接操作Git对象,控制粒度非常细。比如网页端只能把整个文件拖进去,命令行可以只提交某个目录下的部分文件;网页端无法处理冲突,命令行可以清晰地看到冲突标记并逐个解决。我建议每一位开发者都花几个小时把下面这些命令认认真真过一遍,之后你会发现效率提升是成倍的。
本章的目标读者很明确:已经注册了GitHub账号,但还没怎么用过命令行,或者用过一些命令但总是记不清、遇到问题不知道怎么处理的朋友。我会按照一个仓库从建立到日常维护的时间顺序,把最常用、最容易踩坑的命令拆开讲透,并针对每个命令给出我自己的使用习惯,不是那种罗列命令的说明书,而是真正能拿去干活的经验。
1.1 你与GitHub之间只隔着一个本地仓库
要理解Git的工作方式,你先记住三个区域:工作区(Working Directory)、暂存区(Staging Area)、本地仓库(Repository)。工作区就是你电脑上肉眼可见的文件夹;暂存区是Git内部的一个中间区域,用来临时存放你打算提交的内容;本地仓库则是Git替你保存所有提交历史和元数据的地方,它隐藏在你的项目文件夹的.git目录里。
GitHub上的远程仓库是另一个独立的Git仓库,跟你的本地仓库是"双胞胎"关系。你通过git push把本地仓库的提交推送到GitHub,通过git pull把GitHub上的新提交拉回本地。这个设计的好处是:即使没有网络,你也能在本地完整地做版本控制,等有机会再同步到远程。
1.2 网页端能做和不能做的事
网页端能做的操作很有限:新建仓库、查看代码、发Issue、发起Pull Request、简单编辑文件、查看历史记录。它做不到这些事:
- 无法在本地高效地进行批量文件增删改并提交
- 无法处理复杂的分支合并和冲突解决
- 无法执行仓库级的重构和跨分支操作
- 无法提供丰富的本地历史搜索和差异对比
一旦你的项目超过几个文件,老老实实打开终端,用命令管理仓库才是正路。接下来,我们就从准备环境开始,把日常要用到的命令逐个过一遍。
2. 开工前的准备工作:Git安装与身份认证
2.1 安装Git并检查版本
在Windows上,直接从官网下载Git安装包,一路下一步即可。macOS上如果你没有装Homebrew,也可以从官网下载安装包,不过我更喜欢用Homebrew安装:
brew install gitLinux发行版用系统的包管理器安装,比如Ubuntu:
sudo apt update && sudo apt install git安装完成后,在终端跑一句验证命令:
git --version只要显示类似git version 2.40.0的信息,就说明装好了。我建议顺手把Git默认分支名改成main,而不是老旧的master,这个可以通过git config --global init.defaultBranch main设置,下面马上讲。
2.2 配置用户名和邮箱
这是很多新手容易忽略的一步。Git的每次提交都会带上你的提交者信息,如果没配置,提交记录里会显示一串奇怪的系统默认ID,而且你之后推送时可能因为身份不一致被GitHub拒绝。配置命令很简单:全局配置只需要做一次,以后所有仓库都生效。
git config --global user.name "你的名字" git config --global user.email "你的邮箱@example.com"验证配置是否成功:
git config --global --list这里有个小坑:用户名和邮箱不要求一定和GitHub注册信息完全一致,但推荐一致,这样提交记录才能关联到你的GitHub账号。如果某个仓库想用不同的身份,可以在仓库内不带--global重新配置一次,会覆盖全局配置。
2.3 SSH密钥还是HTTPS令牌:我推荐的方式
访问GitHub仓库有两种常规认证途径:HTTPS和SSH。HTTPS方式每次需要输入用户名和密码(现在GitHub已经不允许用账号密码直接推代码了,需要创建Personal Access Token);SSH方式则通过密钥对完成认证,配置一次之后可以免密推送。
我自己一直用SSH,因为它在长期使用中更省心。生成SSH密钥的命令:
ssh-keygen -t ed25519 -C "你的邮箱@example.com"一路回车,生成后会在~/.ssh目录下得到id_ed25519(私钥)和id_ed25519.pub(公钥)两个文件。然后你需要在GitHub网页上把公钥添加进去:Settings -> SSH and GPG keys -> New SSH key,把.pub文件里的内容复制粘贴进去。
添加完之后测试连接:
ssh -T git@github.com看到Hi username! You've successfully authenticated...就说明通了。这样之后克隆仓库时,一律用SSH地址格式(git@github.com:用户名/仓库名.git),不用再反复输入凭据。
提示:私钥文件相当于你的身份证,绝对不要泄露给任何人。
3. 仓库获取三件套:初始化、克隆、远程关联
3.1 从零开始:git init 与本地目录变仓库
假设你有一个本地项目文件夹,里面写了一些代码,现在你想把它托管到GitHub上。第一步是在这个文件夹内初始化Git仓库:
cd 你的项目目录 git init单是这条命令不会产生任何提交,它只是在当前目录创建了.git隐藏目录,意味着这个目录进入了Git管辖范围。我用git init的时候,通常会先想清楚是否要保留这个文件夹原有的历史——如果不保留,直接初始化就行;如果是从其他地方拷贝来的,建议先清理不必要的文件。
初始化之后,我习惯立刻做一次初始提交,用来记录项目起点:
git add . git commit -m "Initial commit"git add .把所有文件加入暂存区,git commit -m提交并附上说明。关于提交说明怎么写,我的经验是:动词开头,说明"做了什么",比如Add login API、Fix memory leak,不要写"修改了一些问题"这种没营养的话。
3.2 克隆现有仓库:git clone 的常见姿势
如果项目已经在GitHub上,你想把代码拉到自己电脑上,就用git clone <仓库地址>。
git clone git@github.com:用户名/仓库名.git执行后会在当前目录生成一个仓库名同名的文件夹,并自动完成远程地址的关联,本地分支也会自动跟踪远程分支。这个命令非常方便,不需要你先git init再git remote add。
克隆时还有几个实用参数:
-b 分支名:只克隆指定分支,比如git clone -b dev git@github.com:用户名/仓库名.git,适合只关心里程碑分支的场景。--depth 1:浅克隆,只拉取最近一次的提交历史,适合拉取大型仓库或只读代码的场景。历史被压缩,能极大节省时间。不过要注意,浅克隆之后没法看完整历史,后续推送也可能受限,所以接手要长期开发的仓库最好不要用深度克隆。--recursive:如果仓库包含子模块(submodule),加上这个参数会一并拉取子模块内容。
克隆完成后,一般先跑一句git status看看工作区状态,确认干净后再动手改代码。
3.3 给本地仓库绑定远程:remote add 的实操细节
有些场景下你已经有一个本地仓库了,想要把它关联到GitHub上新建的仓库。此时先在GitHub网页上新建一个空仓库(不要勾选自动生成README,否则会产生初始提交,容易和你本地仓库历史冲突),然后回到本地执行:
git remote add origin git@github.com:用户名/仓库名.gitorigin是默认的远程仓库别名,你也可以叫别的名字,但约定俗成大家都会用origin,我建议遵循习惯。绑定之后,用git remote -v查看当前关联的远程地址:
git remote -v输出会显示fetch和push两条URL。这里有一个常见的错误:如果之前已经把仓库推送到旧地址,现在要更换,直接再执行一次git remote add会提示remote origin already exists。正确的做法是:
git remote set-url origin 新地址或者先删除再添加:
git remote remove origin git remote add origin 新地址我一般用set-url,因为它更直接,还能保留跟远程仓库相关的跟踪状态。
4. 日常提交闭环:从工作区到远程仓库
4.1 查看状态与差异:status, diff, log
整个日常开发中,最常用的命令是git status。我几乎每改几个文件就会跑一次,它告诉你当前工作区哪些文件被修改了、有没有新增未跟踪的文件、暂存区里有什么东西。形象地说,它是你当前仓库的情报员。
git status显示结果里有几个关键词:Changes not staged for commit表示工作区有改动但还没暂存;Changes to be committed表示已经有文件暂存,等待提交;Untracked files表示新增但Git还没管理的文件。
如果想看文件具体改了什么,用git diff:
git diff它会用对比的方式列出工作区与暂存区之间的差异。注意,git diff默认只看未暂存的改动;要看已经暂存的内容,需要加--cached参数:
git diff --cached这两个命令是排查问题时的利器。比如你怀疑某个文件被改错了,可以先git diff 文件名确认改动,再决定是否提交。
查看提交历史用的是git log,基础用法:
git log它按时间倒序列出每次提交的哈希值、作者、日期和提交信息。git log有很多好看的格式,我常用--oneline --graph --decorate:
git log --oneline --graph --decorate --all这个组合会用简洁的一行显示每个提交,并用字符绘制出分支结构,同时标注出分支指针和标签。我在梳理项目脉络时必用它。
4.2 提交对象:add, commit 的正确用法
提交可不是随便git commit -m就完了。要理解,我们每次提交都对应一次明确的版本变更,所以提交粒度要控制得当。我的习惯是按照功能模块拆分,比如这个提交只做用户登录接口,那个提交只修了样式问题,避免把所有改动混在一个提交里。
添加文件的命令有几种:
git add 文件名 # 添加单个文件 git add src/ # 添加某个目录 git add . # 添加当前所有改动 git add -u # 只更新已跟踪文件的改动,不包括新文件 git add -A # 添加一切,包括删除和新增新手最常见的坑是:用了git add .把不该提交的文件(比如本地配置文件、密钥)也加进去了。避免这个问题最好的办法是配置.gitignore文件,把编译产物、系统文件、环境变量文件等都列进去。我通常在项目一开始就写好它,防止手滑。
提交命令本身:
git commit -m "提交说明"如果你忘记暂存某些文件,也可以直接写git commit -am "提交说明",但这个-a只对已跟踪文件有效,新文件还是需要git add。如果你写提交信息时没带-m,Git会打开一个文本编辑器(可能是Vim或别的),如果不会操作Vim会卡住。这种情况不要慌,按一下i进入编辑模式,写下内容后按Esc,输入:wq回车退出即可。我还是建议养成带-m的习惯。
4.3 推与拉:push, pull, fetch 的区别与协作习惯
本地做完提交,需要同步到GitHub,用推送命令:
git push origin 分支名如果本地分支还没有对应的远程分支,第一次推送时需要在git push后面加上-u参数,意思是建立追踪关系:
git push -u origin main设置过追踪关系之后,后续直接git push就能推了。这里注意,origin是远程别名,main是本地分支名。如果你本地分支名和远程不一致,比如你想把本地的dev分支推到远程的develop:
git push origin dev:develop从远程拉取更新时,有两个指令容易混淆:git pull和git fetch。
git fetch只把远程最新的提交下载到本地仓库,但不会自动合并到你当前的工作分支。它更安全,可以让你看看远程有什么变化,再决定如何合并。
git pull则等于git fetch加git merge(在某些配置下也可能是git rebase),它执行完后会直接尝试把远程分支合并进当前分支,如果存在分叉,可能会产生冲突。
在多人协作时,我习惯先git fetch,然后查看差异,再手动合并;但在单人或小团队快速迭代场景下,直接用git pull更高效。如果要避免pull自动创建合并提交,可以用git pull --rebase,它会把本地未推送的提交按线性方式重新编排。但--rebase会改写提交历史,对不熟悉的新手不友好,所以我建议初次接触的人先用普通git pull。
5. 分支与合并:多人协作的命脉
5.1 分支的创建与切换
分支是Git最强大的功能之一。它让你可以在同一时间线上同时发展多个互不干扰的版本。在GitHub仓库的日常维护中,你在main主干上保持稳定版本,在功能分支上开发新特性,成熟后再合并。
创建分支:
git branch dev这条命令只是创建了dev分支,你仍然停留在当前分支。创建并切换分支,我更推荐:
git checkout -b dev之间还有一条命令:
git switch -c dev我个人的偏好是使用switch系列,语义更清晰:git switch dev切换分支,git switch -c dev新建并切换。毕竟checkout这个词在Git里语义繁多,容易混淆。
查看所有本地分支:
git branch带-a可以同时查看远程分支:
git branch -a5.2 合并与冲突处理
开发完功能分支,要把改动合并回main,先切换回目标分支,再执行合并:
git switch main git merge dev如果两个分支修改了同一文件的同一区域,Git无法自动决定保留哪个,就会报告冲突。此时git status会列出冲突文件,比如:
both modified: src/index.js打开这个文件,你会看到类似这样的冲突标记:
<<<<<<< HEAD const color = 'red'; ======= const color = 'blue'; >>>>>>> dev<<<<<<< HEAD到=======之间是当前分支(main)的内容,=======到>>>>>>> dev之间是要合并分支(dev)的内容。你需要手动选择保留哪部分,或者把两边修改合理合并,然后删掉冲突标记,再执行git add 文件名,最后提交。
解决冲突是整个Git操作中最需要经验的环节。我的建议是:遇到冲突不要慌,并不代表你代码写错了,只是说明两个分支确实动了相同的地方。先理解两边改动含义,再决定取舍。对于无法确定的冲突,建议和改动该文件的同事沟通,不要在冲突文件上猜来猜去。
5.3 删除分支与清理工作
合并完成后,功能分支就完成了使命,可以删除:
git branch -d dev如果分支从未合并过,-d会拒绝删除,需要强制删除:
git branch -D dev删除远程分支用git push:
git push origin --delete dev这里有个细节:删除远程分支时,本地对该分支的跟踪引用会保留,但git branch -a不会再显示它。如果需要同步清理本地的远程分支缓存,用:
git remote prune origin或者直接拉取时自动清理失效的远程分支:
git pull --prune6. 标签、发布与版本管理
6.1 创建与推送标签
GitHub仓库的Release功能通常依赖Git标签。标签是某个稳定提交的命名指针,常用于标记发布版本,比如v1.0.0、v2.3.1。
创建一个轻量标签,直接指向当前提交:
git tag v1.0.0创建带注释的标签,记录标签者、日期和信息,推荐方式:
git tag -a v1.0.0 -m "Release version 1.0.0"-a表示annotated,这种标签包含更完整的元信息,适合正式发布。轻量标签适合临时标记。
查看已有标签:
git tag标签默认不会推送到远程,需要单独推送:
git push origin v1.0.0想一次性推送所有标签:
git push --tags6.2 删除本地和远程标签
如果标签打错了,删除本地标签:
git tag -d v1.0.0远程标签删除:
git push origin :refs/tags/v1.0.0或者用新版语法:
git push origin --delete tag v1.0.0删除远程标签时,本地可能仍然残留对它的远程跟踪引用,用git fetch --prune --tags清理会比较彻底。
7. 高频踩坑场景与抢救方案
7.1 误提交大文件或敏感信息怎么办
很多人会把node_modules、密钥文件误提交到GitHub上。如果只是本地提交但还没推送到远程,很好处理:如果还没有提交,直接取消暂存即可;如果已经提交但还没推送,用软重置到上一个提交:
git reset --soft HEAD~1这个命令会撤销最近一次提交,但保留你的所有改动在暂存区。然后你就可以从暂存区移除误提交的文件,再重新提交。
如果已经推送到远程,情况就比较麻烦了,因为即使删除文件重新提交,之前的错误提交仍然保存在Git历史中。这时需要用git filter-repo这类工具重写历史,或者至少用git rm --cached从Git追踪中移除文件。我个人强烈建议在推送到GitHub之前,认真检查git status,并且总要把敏感信息放到环境变量文件并加入.gitignore。一旦敏感信息泄露到公共仓库,正确的做法是立刻把相关秘钥作废并重新生成,而不是指望删除历史能抹除痕迹。
7.2 push被拒:非快进提交的三种应对
当你执行git push时,如果报错! [rejected] ... (non-fast-forward),意味着远程分支上有你本地没有的新提交。通常是因为别人已经推了新代码,你需要先把远程更新拉下来:
git pull origin main合并后重新推送。如果你确定要覆盖远程内容(比如只是个人项目且你已经备份好了),可以用强制推送:
git push -f origin main强制推送会改写远程仓库历史,在多人协作的项目里是非常危险的操作,会造成其他人的本地历史与远程不一致,所以只有在万不得已时才使用。如果语意上不希望完全覆盖,而是想用本地历史替换远程,推荐用--force-with-lease,它会在确认远程分支没有新增提交时才强制推送,比单纯的-f安全很多:
git push --force-with-lease origin main我这里有一条工作准则:在团队仓库中,永远不要对公共分支使用强制推送。如果确实需要整理历史,先跟团队沟通,创建新的分支推上去,让同事再合并。
7.3 误删本地分支如何恢复
我曾有一次直接把一个还没合并的分支用-D删了,瞬间后悔。Git其实给了后悔药:分支本质上是某个提交的引用,只要那个提交还在对象库里,就可以找回。
首先用git reflog查看操作记录:
git reflog这里能看到一系列HEAD移动的历史,包括删除分支前所在提交的哈希。找到那个分支最后一次的commit哈希,比如abc1234,然后用以下命令从该哈希创建新分支:
git branch dev abc1234这样分支就回来了。git reflog保留的是本地仓库中的引用变化记录,默认过期时间为90天,所以只要你在90天内操作过,基本上都能找回来。
这个经验说明一个问题:删除分支前,最好先确认该分支是否已经合并且不再需要。工程上,我通常会在删除前先跑git branch --merged看一下哪些分支已经合并,再决定删除哪些。
最后说几句
写到这里,我回头看了一下,其实GitHub仓库常用命令就那么二三十条,但每一条背后都藏着版本控制的理念。我从实际使用中总结的最大心得是:命令背熟只是第一步,更重要的是养成好的操作习惯——每次改动前先git status看清楚,提交时把粒度切小,推送前检查diff,遇到冲突不暴躁。这些习惯比记住任何命令都值钱。
如果你刚开始接触,建议先从一个小项目开始,把上面的命令逐条敲一遍,哪怕故意制造几次错误再修复,也比只看教程强。等你把这些命令变成肌肉记忆,再打开GitHub网页,你会觉得那些光鲜的按钮背后,其实都是你熟悉的命令在帮你干活。