一、Git 解决的是什么问题
版本控制器记录了文件历史与发展过程、方便多人协同的系统,最主流的就是 Git;只是它只能跟踪文本文件的改动,知道第 5 行加了个词,而图片视频这类二进制文件只会告诉你体积变了,改了什么无从得知。
二、安装与最小配置
bash
sudo apt install git # Ubuntu;CentOS 用 yum install git
git --version # 验证安装
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
装完第一件事是配置身份,否则提交会报错;加 --global 表示这台机器上所有仓库都生效(全局)。

三、三个区与 .git 目录
- 工作区:写代码、放文件的目录
- 暂存区:英文
stage或index,文件就是.git/index,所以也叫索引 - 版本库:工作区里的隐藏目录
.git,里面的文件才受 Git 跟踪
执行 git init 会看到 Initialized empty Git repository。.git 里有四样值得记住:index 是暂存区,HEAD 里存着 ref: refs/heads/master,refs/heads/master 存该分支最新的 commit id,objects 是对象库。
关键结论:往目录里粘贴文件不等于向仓库加文件,必须 add + commit 才算入库

四、add 与 commit 的边界
常用操作:
git add [file]把改动加入暂存区git commit -m "描述"再把暂存区整体写进版本库- 查状态用
git status - 看改动用
git diff(暂存区&工作区的区别) - 看历史用
git log --pretty=oneline
只有被 add 过的改动,才会被这次 commit 带走
git diff使用:

五、objects 里究竟存了什么
commit id 不是 1、2、3 递增的数字,而是 SHA1 算出的十六进制串,前 2 位是目录名、后 38 位是文件名,就藏在 .git/objects/ 下;文件被加密不能直接看,用 git cat-file -p 解开:
tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7
parent c61428926f3853d4ec6dde904415b0e6c1dabcc6
author hyb91 <2689241679@qq.com> 1683343652 +0800
add 3 files
展开 tree 能看到各文件对应的 blob,再展开就是文件内容。所以一次 commit 是"提交对象 → tree → blob"的树状结构,内容相同则 blob 相同
查找object:两部分组成(目录+文件名)


六、版本回退:reset 三兄弟与 reflog
版本写法:HEAD 是当前版本,HEAD^ 与 HEAD~1 是上一个版本,HEAD^^ 是上上一个,也可以直接写 commit id,甚至只写前 7 位。
bash
git reset [--soft | --mixed | --hard] [HEAD]
--soft:只回退版本库,工作区与暂存区不动;--mixed:默认参数,回退版本库与暂存区,工作区不变;--hard:三者一起回退,工作区有未提交代码时慎用,回滚了就找不回来。
用了 --hard 想再回去,git log 已看不到被丢弃的提交,git reflog 记录了本地每条命令,从中把 id 捞出来即可。
版本回退:

暂存区和工作区都回退到指定版本

reflog查看操作记录:

七、撤销修改与删除文件
撤销分三种情况:
- 只改了工作区、没 add:
git checkout -- [file]回到最近一次 add 或 commit 的状态,--不能省; - 已经 add、没 commit:先用
git reset HEAD [file]把暂存区退回,再用上一条清掉工作区改动; - add 也 commit 了:
git reset --hard HEAD^,前提是还没推送到远程。
删除文件也是一次修改。直接 rm file5 只删了工作区,git status 提示 deleted,仓库里并没少;误删用 git checkout -- file5 恢复,确实要删则 git rm file5 再 commit。
撤销修改:

强制删除文件:(同时从工作区和暂存区中删除)

八、分支:一个会移动的指针
分支像科幻片里的平行宇宙:这条线上的你在写 C++,那条线上的你在写 Java,互不干扰,将来一合并,两边成果都是你的。每次提交都串成一条时间线,这条线就是一个分支,默认那条叫 master。
HEAD 严格来说不指向提交,而是指向 master,由 master 去指向提交:
bash
cat .git/HEAD # ref: refs/heads/master
cat .git/refs/heads/master # 5476bde...(当前最新 commit id)
8.1 创建、切换与查看
git branch:查看本地所有分支,带*的是当前分支;git branch dev:新建分支,它和 master 指向同一个 commit;git checkout dev:切换;git checkout -b dev3则是新建并切换。
切换后工作区内容会跟着变:在 dev 上提交后切回 master,新增内容不见了,因为 master 指针根本没动;再切回 dev 又回来了。
创建和切换分支:

8.2 合并与 Fast-forward
在 master 上执行 git merge dev;若 master 期间没动过,结果是 Fast-forward,直接把 master 指到 dev 的当前提交,速度极快,合并完用 git branch -d dev1 删掉分支。但快进合并后再删分支,历史里就看不出曾有过分支;加 --no-ff 禁用快进,merge 会多生成一个 commit,痕迹得以保留。
合并分支:

8.3 冲突
两边都提交过时,合并必然冲突,Git 用 <<<<<<<、=======、>>>>>>> 标出双方内容;手动改完后还必须重新 add 并 commit,冲突才算真正解决。
合并冲突:

解决冲突:

使用fastforward策略:(不推荐)


禁用fastforward策略:(推荐,新建提交,可以看到提交分支,不会因为分支的删除丢失)


8.4 stash:把现场存起来
正在 dev 上开发一半,突然发现 master 有 bug 要修,代码却还没法提交。此时,需要用git stash 把工作区现场储藏起来,这样git status 立刻干净,可以放心去修 bug;回来用 git stash list 查看、git stash pop 恢复(该条同时删除;用 apply 则保留,需要再次 drop 清除stash)。
修bug的步骤:正在开发的dev分支设置stash -> 另外新建fix_bug分支 -> 修好后在master分支合并删除fix_bug分支 -> 在dev分支与新的master分支合并,恢复stash -> 解决冲突 -> 回到master分支合并没有冲突的dev分支

8.5 分支到底有什么用
要开发一个新功能、需要两周:写一半就提交,不完整的代码会影响别人;等全写完再提交,又怕丢掉每天的进度。有了分支就不怕了------你在自己的分支上干活,别人看不到,想提交就提交,开发完再合并回去;无论版本库有一个还是一万个文件,Git 创建、切换、删除分支都在一秒内完成。
九、总结
- 三区:工作区改文件、暂存区做缓冲、版本库落库,
add与commit缺一不可; - 对象库:commit 里包着 tree、tree 里包着 blob,内容相同则 blob 相同;
- 回退:
reset的 soft/mixed/hard 决定动哪几个区,id 丢了找reflog; - 撤销与删除:分三种情况对症下药,删文件走
git rm加commit; - 分支:
HEAD指分支、分支指提交,合并有快进与--no-ff两种,冲突须手动改完再提交。