写代码基本绕不开 Git。但它真不只是「备份代码」这么件事——往深了说,它管的是你怎么记录每一次改动、怎么把改坏的救回来、怎么跟别人对着同一套代码一起干活。

这篇我从最常用的日常操作讲起,再说说别人改了代码、你只想拉那点增量该怎么办(顺便聊聊第一次克隆超大仓库的坑),最后是团队里怎么合代码、怎么救回误删的东西。都是能直接照抄的命令,平时当手册翻也顺手。

一、先搞懂 Git 的三个区和一个链

很多人被 Git 绕晕,是因为分不清「文件到底在哪」。记住三个区域就清晰了:

1
2
3
4
5
6
7
8
9
10
工作区(你正在编辑的文件)
│ git add
▼
暂存区(本次要提交的清单,staging)
│ git commit
▼
本地仓库(Committed 历史,一串 commit 串成链)
│ git push
▼
远程仓库(GitHub / GitLab 上的那个)
  • 工作区:你电脑上真实能看到的文件。改完不会自动进 Git。
  • 暂存区:git add 把「要提交哪些改动」先放进待办清单。
  • 本地仓库:git commit 把暂存区的清单正式记成一条历史记录(commit)。
  • 远程仓库:git push 把本地的提交同步到服务器,git pull/git fetch 把别人推的拉回来。

核心的一个链:每个 commit 都指向前一个 commit,head 指向最新那条。所有分支、合并、回退,本质都是「移动这个链上的指针」,而不是复制文件——这也是 Git 快、省空间的原因。

二、第一次上手

装好 Git 后(Windows 装 Git for Windows,Linux apt install git),先设置身份,否则提交没署名:

1
2
git config --global user.name  "你的名字"
git config --global user.email "you@example.com"

把一个目录变成仓库 + 第一次提交:

1
2
3
4
cd 你的项目目录
git init # 让这个目录成为仓库
git add . # 把当前所有改动加入暂存区
git commit -m "first commit" # 记成一条历史

git init 之后目录里会多一个隐藏的 .git 文件夹——所有版本历史都存在这。想彻底删掉 Git 记录,删掉这个文件夹即可。

三、日常基本操作

这几个命令占了日常 90% 的使用:

1
2
3
4
5
6
7
8
git status                # 看现在哪些文件改了、哪些在暂存区(最常用的一个)
git add <文件> # 把某个文件的改动放进暂存区
git add . # 全部放进
git commit -m "说明" # 提交暂存区里的改动
git log --oneline # 看提交历史(一行一条,很清爽)
git log --graph --oneline # 带分支图
git diff # 看工作区里改了什么(还没 add 的)
git diff --staged # 看暂存区 vs 上一次提交的区别

git status 会把文件分成三组给你看:

  • Changes not staged:改了但没 add
  • Changes to be committed:已 add 等 commit
  • Untracked files:新增的、从没进过 Git 的文件

四、分支:Git 最核心的能力

分支的本质:一个可以移动的指针,指向某条 commit。main(或 master)是默认分支,你新建分支就是「另起一条平行线」,互不干扰。

1
2
3
4
5
6
7
git branch                # 看所有分支,* 是当前所在
git branch <名字> # 创建分支(不切换过去)
git switch <名字> # 切换分支(新写法)
git checkout <名字> # 切换分支(老写法,也行)
git switch -c <名字> # 创建 + 一次性切过去(最常用)
git merge <别的分支> # 把别的分支合并到当前分支
git branch -d <名字> # 删除分支

合并怎么发生:分两种情况——

  • Fast-forward(快进):当前分支没新提交,直接让指针往前移动到头,历史是一条直线,干干净净。
  • 三方合并:两边各自有提交,Git 把两个分支的改动「嫁接」出一个新的合并提交。如果两边改了同一文件的同一行,就有冲突,需要你手动定夺(见第六节)。

日常推荐流程:在 dev 上干活,git switch -c feature-xxx 开个功能分支,做完 git switch dev && git merge feature-xxx。

五、远程与协作:clone / fetch / pull / push

1
2
3
4
5
6
git clone <仓库地址>      # 第一次把远程仓库整个下载到本地
git remote -v # 看远程仓库地址(origin 是默认别名)
git push # 把本地当前分支的提交推到远程
git pull # = git fetch + git merge(拉下来并合并)
git fetch # 只拉取远程的最新提交,不自动合并(更安全)
git branch -r # 看远程有哪些分支

fetch 和 pull 的区别:

  • git fetch:把远程的新提交下载到本地,但不碰你正在改的代码。你想先看看、再决定怎么合,用这个。
  • git pull:下载 + 直接合并到当前分支。图省事用这个,但可能立刻产生冲突。

多人共用同一个分支时,改动前先 git pull 拿到最新,改完再 git push,push 被拒(别人比你高)就先 pull 再 push。

六、冲突怎么解决

git merge 或 git pull 时说 CONFLICT (content),就是两边改了同一处。Git 会在文件里标记:

1
2
3
4
5
<<<<<<< HEAD
你当前分支的这一段
=======
对方(被合并进来的分支)的那一段
>>>>>>> feature-xxx

解决办法只有一步:手动改这个文件,留下想要的内容,删掉 <<<<<<<、=======、>>>>>>> 这几行标记,然后:

1
2
git add 那个文件          # 标记为已解决
git commit -m "merge 解决冲突"

越早把改动分开到独立分支、越频繁同步,冲突越少、越好解。

七、撤销与回退:救回误操作

这是新手最慌、也最该掌握的部分。分清「想撤销哪种改动」。

① 文件改了还没 add,想丢弃:

1
2
git restore <文件>        # 回到上一次提交的状态(新命令)
git checkout -- <文件> # 老写法,效果一样

② 已经 add 了,想移出暂存区(不想提交了):

1
git restore --staged <文件>

③ 已经 commit 了,想撤销这次提交但保留改动:

1
git reset --soft HEAD~1   # 撤销最近1次提交,改动留在暂存区

④ 提交错了想彻底抹掉(此后的历史重写):

1
git reset --hard HEAD~1   # 撤销最近1次提交,改动也没了(慎用!)

⑤ 已经 push 到远程了,想回退——reset 会强制改历史、影响别人,别用。用 revert:新增一个反向提交把那次改动抵消掉:

1
git revert <commit的hash>

⑥ 临时藏起手上做了一半的改动:

1
2
3
git stash                 # 把未提交的改动暂存起来,工作区清空
git stash pop # 恢复回来
git stash list # 看存了哪些

⚠️ 一条救命常识:git reflog 能看到你最近所有「指针移动」的记录,即使 reset --hard 删掉的分支/提交,也能从 reflog 里找回。几乎所有「误删」都能用 git reflog + git reset --hard <hash> 救回来,别慌。

八、增量与部分克隆:别人改了代码,我只拿改过的部分

这是很多人问的「能不能只下载别人改的部分」。答案是:Git 本身就是增量传输的,而且是自动的。

8.1 有本地仓库时:pull 天然只下增量

你 git clone 过一次后,本地有一个完整对象库。之后别人改了并 push,你再 git pull / git fetch,Git 只传输你本地缺的那几个 commit 和文件对象,绝不会重新下载整个仓库。所以:

只要你已经 clone 过,git pull 就自动只拿别人改过的部分,不用任何特殊配置。

Git 的传输是按「对象」打包的,缺什么传什么,跟「全量/增量」无关——它天生就是增量的。

8.2 第一次克隆超大仓库:用参数少下载点

真正的痛点是不想「第一次就全量 clone」一个几个 GB 的 monorepo。三个参数按需省流量:

参数 作用 代价
--depth 1 只下载最新一次提交的快照 没有历史,git log 只剩一条
--filter=blob:none 下载全部提交和目录结构,但文件内容按需下载 用到哪个文件才下哪个,历史完整
--sparse 只检出指定目录 仓库其余部分不进本地

最省组合(只要最新代码 + 只挑某个目录):

1
2
3
git clone --depth 1 --filter=blob:none --sparse <仓库地址>
cd 仓库
git sparse-checkout set src docs # 只保留这两个目录在本地

注意:--filter(partial clone)和稀疏检出需要服务器支持才行(GitHub / GitLab 都支持)。如果你用的是本地镜像代理(比如某些加速镜像),很可能不支持 filter,那部分参数会失效甚至报错——这种情况退回到 --depth 1(纯客户端行为,服务器无要求,一定有效)。

8.3 只想看别人改了哪些文件(不想下载)

1
2
git fetch                          # 拉远程提交,但不合并
git diff --stat HEAD..origin/main # 看本地 HEAD 和远程 main 差了哪些文件、多少改动

想精确看某个文件改了什么,再针对性查看,按需取用。

九、忽略不想进 Git 的东西:.gitignore

有些文件不该进版本库(依赖、密钥、本地配置、构建产物)。在项目根目录建一个 .gitignore:

1
2
3
4
5
6
node_modules/
build/
*.log
.env
local.properties
!keep.log # 用 ! 取反,强制保留某个文件

这样 git add . 会自动跳过这些,不会误提交大文件或密钥。⚠️ 密钥千万别传——一旦推到公开仓库就等于泄露,历史里也删不干净。

十、团队协作的标准流程(分支 + PR)

现在协作基本都走「功能分支 + Pull Request」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 同步最新
git switch dev && git pull

# 2. 开一条功能分支
git switch -c feat-xxx

# 3. 干活,分多次提交
git add . && git commit -m "xxx"
git add . && git commit -m "yyy"

# 4. 推到远程这条分支
git push -u origin feat-xxx

# 5. 在 GitHub / GitLab 上把 feat-xxx 发起 Pull Request 合到 dev
  • 别人 review 后合并,分支可删。
  • 你的分支落后于 dev?在分支上 git merge dev 或 git rebase dev 同步。
  • rebase vs merge:merge 保留众人历史(会有合并分叉),rebase 把分支「垫」到 dev 顶端(历史更线性),各有取舍。团队有约定就按约定来。

十一、速查表

需求 命令
看状态 git status
暂存改动 git add .
提交 git commit -m "..."
看历史 git log --oneline
开分支并切换 git switch -c 分支名
合并分支 git merge 分支名
拉 + 合并 git pull
只拉不合并 git fetch
推送 git push
丢弃工作区改动 git restore <文件>
撤销最近提交(留改动) git reset --soft HEAD~1
撤销并抹掉最近提交 git reset --hard HEAD~1(慎用)
远程回退(补反向提交) git revert <hash>
暂存手头改动 git stash
找回误删 git reflog
只看改了哪些文件 git diff --stat HEAD..origin/分支

结语

Git 的核心不是你记多少命令,而是理解那个 「工作区 → 暂存区 → 提交 → 远程」的链和 「分支就是可移动指针」。懂了这两点,其余命令都是围绕它们转的便捷工具。

日常用熟:status、add、commit、log、switch、merge、pull、push 这八个就够绝大多数场景;reset、revert、stash、reflog 是用来救命的,记住它们的存在,关键时刻不慌。