🔥keyipatience:个人主页
🎬作者简介:C/C++后端开发学习者
🌟专栏传送门:《c++》《linux》《c++高阶数据结构》《c++数据结构与算法》《前端学习》
⭐️patience is key in life
Git 初识
传统保存版本方式的痛点
平时写文档、写代码,很多人靠复制文件 + 改名保存版本:报告-v1、报告-v2、报告-最终版、报告-究极进化版…… 问题: 文件越堆越多 时间久了,记不清每个版本改了哪些内容代码项目同样有这个麻烦。
版本控制器(版本控制系统)
定义:记录工程每一次改动、管理版本迭代、查看文件历史,还支持多人协同开发的系统。
现在最主流的版本控制器:Git
- Git 可以管理任意格式文件:doc、excel、图纸、代码等
- 对程序员:核心用途 →管理项目源代码
三、重要注意点
- 文本文件(txt、网页、源代码):Git 可以精确跟踪改动 能看到:第几行新增内容、第几行删除内容。
- 二进制文件(图片、视频):Git 只能存文件,看不到里面改了什么仅能感知文件大小变化(比如 100KB → 120KB),无法识别图片内部修改。
Linux 下安装 Git
CentOS
1.检查是否已安装
git提示-bash: git: command not found→ 代表未安装
2. 安装命令
sudo yum -y install git3.验证 / 查看版本
git --versionUbuntu
1.检查是否已安装
git提示Command 'git' not found→ 代表未安装
2. 安装命令
sudo apt install git -y3.验证 / 查看版本
git --version创建 Git 本地仓库
仓库(repository):用来做版本控制的文件夹(目录),要管理文件版本,必须先创建仓库。
核心命令:
git init //给【你当前所在的这个文件夹】,装上 Git 版本控制。 //先 cd 进项目目录,再 git init。输出提示:Initialized empty Git repository in xxx/.git/→ 初始化成功
初始化仓库后自动创建:master主分支 +HEAD指针(HEAD 指向当前所在分支)后面我们会再讲
初始化后变化
执行ll -a可以看到多出.git隐藏目录
.git目录:Git 用来记录、跟踪、管理仓库所有版本信息的核心文件夹,禁止手动修改里面的内容,容易直接损坏 Git 仓库
git 目录内部结构(简要)
.git/ ├── branches ├── config # 当前仓库配置 ├── description ├── HEAD # 指向当前所在分支 ├── hooks # 钩子脚本(提交前后触发脚本) ├── info ├── objects # 存放所有快照、对象(代码版本数据) └── refs ├── heads # 分支引用 └── tags # 标签引用Git 配置
安装 Git 之后第一件事:配置用户名和邮箱,每次提交记录都会带上这个身份信息
配置命令
# 设置用户名 git config [--global] user.name "Your Name" # 设置邮箱 git config [--global] user.email "email@example.com"--global重点解释
- 带
--global:全局配置,本机上所有 Git 仓库共用这一套名字 + 邮箱(最常用) - 不带
--global:仅当前仓库生效,需要先 cd 进入这个 git 仓库目录,适合不同项目用不同身份
2. 查看所有配置
git config -l3. 删除配置
# 删除用户名配置 git config [--global] --unset user.name # 删除邮箱配置 git config [--global] --unset user.email工作区、暂存区、版本库
- 工作区(Working Directory)你电脑上写代码、放文件的项目文件夹,就是工作区。
- 暂存区(stage /index,索引)在
.git/index文件里,是一个临时的 “待提交清单”。 - 版本库(repository,仓库)就是目录里的隐藏
.git文件夹,不属于工作区;Git 就在这里保存所有版本历史,可以回滚、追踪修改。
流转流程
工作区 → git add → 暂存区 → git commit → 版本库(master分支)git add:把工作区的修改,放到暂存区,更新索引git commit:把暂存区里的内容,正式提交写入版本库
添加文件--场景一
git add + git commit + git log
git add:把文件从工作区送到暂存区
三种常用写法:
# 1. 指定单个/多个文件 git add file1 file2 # 2. 添加整个目录(包含里面所有子文件、子目录) git add 目录名 # 3. 【高频】添加当前目录下所有改动的文件 git add .git commit:把暂存区内容写入版本库(生成一条版本记录)
两种写法:
# 1. 提交暂存区里全部内容(最常用) git commit -m "这里写提交说明" # 2. 只提交暂存区里指定的几个文件 git commit file1 file2 -m "这里写提交说明"m的含义:message,提交备注,不能省略
作用:写清楚这次改了什么,方便以后看历史、回滚版本,是给自己 / 团队看的。
eg:
git commit -m "commit my first file" git commit -m "add 3 files"完整实操示例 1(单个文件)
# 1.新建/编辑 ReadMe vim ReadMe # 2.加入暂存区 git add ReadMe # 3.提交到版本库,写备注 git commit -m "commit my first file"提交成功后,终端输出:1 file changed, 2 insertions(+)含义:1 个文件改动,新增 2 行内容。
完整实操示例 2:多次 add,一次 commit(很常用)
touch file1 file2 file3 # 新建3个文件 git add file1 git add file2 git add file3 git commit -m "add 3 files"重点:add 可以分开多次执行,文件都堆在暂存区,最后一次 commit 统一存入版本库。
git log:查看提交历史记录
默认
git log输出内容:
commit 一串哈希值:commit id(版本号)- Author:提交人(就是前面
git config配置的 name 和 email) - Date:提交时间
- 提交 message(自己
-m写的那句话)
顺序:最新的提交在最上面,旧的在下面
精简模式(推荐,看日志清爽很多)
git log --pretty=oneline效果:一行展示一条记录,左边是 commit id,右边是提交备注。
对比:
查看暂存区和版本库文件内容
查看【暂存区】里文件完整内容
git show :文件名 # 例子 git show :ReadMe:冒号就代表暂存区(index)
查看【版本库(HEAD)】里文件完整内容
git show HEAD:文件名 # 例子 git show HEAD:ReadMe深入 .git 目录(底层原理)
tree .git //查看整个.git 目录结构几个核心文件 / 目录
① index
index = 暂存区(stage)git add之后,改动信息就写到这个index文件里。 它保存一份「待提交的文件索引清单」。
② HEAD
HEAD 本质是一个指针文件,记录当前在哪一个分支。
cat .git/HEAD输出:ref: refs/heads/master意思:HEAD 现在指向master分支。
HEAD 不是直接存 commit id,它先指向分支,分支再指向 commit id。
③ refs/heads/master
cat .git/refs/heads/master里面存放的就是当前 master 分支最新一条的 commit id(哈希串),master 分支就是靠这个文件记住:我现在停在哪一个版本。
什么是commit id,有什么用?
commit id是SHA1 哈希算法生成的一串十六进制字符串(像:23807c5...56eed6)
- 每一次
git commit,这个文件里的哈希串就会更新成新 commit 的 id - 后面回退版本、切换版本,都要用这个 id
存储规则(哈希一共 40 位十六进制):
- 前 2 位 → 作为文件夹名字
- 后 38 位 → 作为文件名例:
23807c536969cd886c4fb624b997ca575756eed6文件夹名叫23,文件名叫807c536969cd886c4fb624b997ca575756eed6
对象文件是 SHA 压缩加密的,不能直接 cat 打开看,要用专门命命令
git cat-file -p 哈希串 //-p = pretty,格式化打印对象内容④ objects 目录(Git 对象库,重中之重)
Git 所有版本数据、文件内容、目录结构,全部存在这里。【存放 commit/tree/blob 三类对象】
三个对象各自是干什么的:
- blob 对象只保存文件内容,不存文件名、不存路径。 同样内容的不同文件,会复用同一个 blob,省空间。
- tree 对象(目录树)代表一个文件夹。 里面记录:这个文件夹下有哪些条目(子文件 / 子目录),每个条目对应:权限、名字、指向 blob 或者子 tree 的哈希。
- commit 对象(提交记录)代表一次版本快照(你一次 git commit)。 里面记录:
- 一个 tree 的哈希(这次提交对应的顶层目录树)
- parent:上一个 commit 的哈希(形成版本链)
- author、committer、提交备注
-m "xxx"
三者的引用关系
commit 对象 ↓(持有一个tree哈希,找到tree对象) tree 对象 ↓(持有若干哈希,找到blob或者子tree对象) blob 对象 或者 子tree对象eg:
commit(23807c...) └── tree(830a8c...) ├── blob(9c9e1f...) → ReadMe 的内容 ├── blob(e60de2...) → file1 的内容 ├── blob(e60de2...) → file2 的内容 └── blob(e60de2...) → file3 的内容完整大图
HEAD (.git/HEAD) ↓ 指向分支引用 refs/heads/master ↓ 里面存最新commit id commit 对象 (在objects里) ↓ 记录顶层tree哈希 tree 对象 (在objects里) ↓ 记录多个blob/子tree哈希 blob / tree 对象 (都在objects里)打印看一看:
//(1)commit 对象(提交对象) git cat-file -p 23807c536969cd886c4fb624b997ca575756eed6 //输出内容: tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7 parent c61428926f3853d4ec6dde984415b0e6c1dabcc6 author hyb01 <2689241679@qq.com> ... committer hyb01 <2689241679@qq.com> ... add 3 files //(2)tree 对象 git cat-file -p 830a8c9feefbdc098bbae2cdc25e5034ce1920d7 //输出类似: 100644 blob 9c9e1f0f6bff3015df71a0063004476f5e6cfd54 ReadMe 100644 blob e60de26bb2d1d6434b8b29ae775ad8c2e48c5391 file1 100644 blob e60de26bb2d1d6434b8b29ae775ad8c2e48c5391 file2 100644 blob e60de26bb2d1d6434b8b29ae775ad8c2e48c5391 file3 //(3)blob 对象 git cat-file -p 9c9e1f0f6bff3015df71a0063004476f5e6cfd54 //直接输出文件原始内容: hello bit一句话流程复盘
修改工作区文件 → git add:生成 blob 存入 objects、更新 index 暂存区 → git commit:生成 commit 对象 + tree 对象,更新 master 分支记录。
添加文件--场景二
git status + git diff
Git 跟踪的是修改,不是整个文件。新增一行、删除一行、改动字符、新建文件,全都算一次修改。
git status 查看仓库状态
git status告诉你:工作区、暂存区里面文件处于什么状态。
第一种输出(修改了文件,但还没 git add)
含义: 文件已经在工作区改动还没有加入暂存区提示给你两个选择:
git add <file>:把改动加入暂存区git restore <file>:扔掉工作区改动,恢复原样
第二种输出(已经 git add,在暂存区等待 commit)
含义:修改已经放到暂存区,准备提交 提示:git restore --staged <file>可以把文件从暂存区撤出来(取消 add)
第三种输出(commit 完成之后)
含义:暂存区空、工作区干净,当前没有未提交改动
git diff 查看具体差异(看改了啥)
git diff [文件名]
git diff ReadMe工作区 vs 暂存区 → 看add 之后又改了、还没再次 add的改动
diff 格式解读(Unix 标准 diff):
diff --git a/Readme b/Readme # git格式diff,a是旧版(暂存区),b是新版(工作区) index c940fc8..3d025e4 100644 # c940fc8旧blob哈希,3d025e4新blob哈希;100644普通文件权限 --- a/Readme # --- 代表旧版本 +++ b/Readme # +++ 代表新版本 @@ -1,2 +1,3 @@ # -1,2:旧片段从第1行,共2行;+1,3:新片段从第1行,共3行 hello world # 无标记:新旧版本都存在,该行不变 hello git # 无标记:新旧版本都存在,该行不变 +hello ysm # + 加号:新版本新增这一行git diff HEAD -- [文件名]
git diff HEAD -- ReadMe对比:版本库(HEAD 指向的最新 commit) VS 工作区不管有没有 add,都能看到和上一次提交相比,一共改了哪些东西(commit肯定就看不到了)
git diff --cached--[文件名]等价git diff --staged
暂存区 vs HEAD(版本库)→ 马上要 commit 提交的内容!
版本回退
回退核心本质:git reset移动HEAD 和分支指针(master),指向历史上某个 commit; 三个参数的区别,就是【暂存区、工作区】要不要跟着一起改动。
先记住一条主线例子: 连续提交 3 次:version1 → version2 → version3
commit1(version1) ← commit2(version2) ← commit3(version3, HEAD/master现在指向三个参数
git reset [--soft | --mixed | --hard] 目标commit| 参数 | 版本库(HEAD / 分支指针) | 暂存区(index) | 工作区 | 特点 |
|---|---|---|---|---|
--soft | ✅ 回退到目标 commit | ❌ 不变 | ❌ 不变 | 只挪指针;改动还在暂存区,可以直接 commit |
--mixed(默认,不加参数就是它) | ✅ 回退到目标 commit | ✅ 重置成目标版本 | ❌ 不变 | 改动从暂存区退回工作区,状态是 modified |
--hard | ✅ 回退到目标 commit | ✅ 重置成目标版本 | ✅ 重置成目标版本 | 危险!工作区直接覆盖,未保存代码丢失(git reflog找回) |
简写写法(定位要回退到哪一个版本)
HEAD:当前最新版本HEAD^:上 1 个版本HEAD^^:上 2 个版本HEAD~1= HEAD^,上一个HEAD~2= HEAD^^,上两个- 注意:都是相对于 HEAD 当前指向的这个 commit往上数。
- 分不清楚也可以直接写完整 / 部分 commit id(只要前缀唯一就行,比如 d95c13f)
举例:
git reset --hard HEAD^回退到上一个版本
怎么知道现在处于哪个版本?
看当前 HEAD / 分支指向(最常用)
git log --pretty=oneline第一行开头就是当前 commit id,括号里HEAD -> master代表现在就在这条提交上。
一个巨大问题:reset --hard 之后,git log 看不到被丢掉的 commit 了!
例子:reset --hard 回到 version2
git log 里不再出现 version3 那条 commit,好像 version3 消失了。 这时你后悔了,想回到 version3 怎么办?
git reflog
git log:看分支提交链上的历史(reset 之后旧 commit 不在链上,就不显示)
git reflog:记录本地 HEAD 所有移动记录!不管你 reset、切换分支,HEAD 每一次挪动都会被记下来,是本地的操作日志。
输出示例:
d95c13f HEAD@{1}: commit: add version3 14c12c2 HEAD@{2}: commit: add version2 cff9d1e HEAD@{3}: commit: add version1这里d95c13f就是消失的 version3 的 commit id! 拿到这个 id,就可以再次 reset 回去:
git reset --hard d95c13f就救回 version3 了。
完整实例:
# 1. 新建仓库(如果还没有) mkdir testgit && cd testgit git init # 第1次提交 version1 echo "hello version1" > ReadMe git add ReadMe git commit -m "add version1" # 第2次提交 version2 echo "hello version2" >> ReadMe git add ReadMe git commit -m "add version2" # 第3次提交 version3 echo "hello version3" >> ReadMe git add ReadMe git commit -m "add version3" git log --pretty=oneline 输出类似: a3c3 (HEAD -> master) add version3 b721 add version2 58de add version1 //案例 1:--hard git reset --hard HEAD^ # 看文件内容 cat ReadMe # 看日志 git log --pretty=oneline //现象: - ReadMe 里面只有 version1、version2,version3 不见了 - log 里看不到 v3 的那条记录了 - HEAD 现在指向 v2 //后悔了!想找回 v3: git reflog //输出: a3c3 HEAD@{1}: commit: add version3 b721 HEAD@{0}: reset: moving to HEAD^ //HEAD@{0}= 现在,HEAD@{1}= 上一步,数字越大越早 //拿到 v3 的 id `a3c3`,切回去: git reset --hard a3c3