Git 文件状态管理:add、commit、status 和 diff
文章目录
- Git 文件状态管理:add、commit、status 和 diff
- 一、Git 管理的是修改,而不只是文件
- 二、文件在 Git 中的常见状态
- 1. 未跟踪状态:Untracked
- 2. 已暂存状态:Staged
- 3. 已提交状态:Committed
- 4. 已修改但未暂存:Modified
- 三、git add 和 git commit:暂存与提交
- 1. git add 的作用
- 2. git commit 的作用
- 3. 多次 git add 可以对应一次 git commit
- 四、一个容易踩坑的例子:只提交了 add 过的文件
- 五、git status:看懂当前仓库状态
- 常见状态总结
- 六、git diff:查看文件具体改了什么
- git status 和 git diff 的区别
- git diff HEAD -- file
- 七、修改文件后的推荐提交流程
- 1. 查看当前状态
- 2. 查看具体差异
- 3. 加入暂存区
- 4. 再次查看状态
- 5. 提交到本地仓库
- 6. 确认工作区干净
- 八、git log 和 commit id:查看提交历史
- commit id 为什么不是简单数字
- 九、从 .git 目录理解 add、commit 和对象存储
- 1. index:暂存区
- 2. HEAD:当前分支指针
- 3. refs/heads/master:当前分支最新提交
- 4. objects:Git 对象库
- 5. commit、tree、blob 对象
- 总结
Git 文件状态管理:add、commit、status 和 diff
前面已经整理了如何创建 Git 本地仓库,也知道了 Git 中几个非常重要的区域:
- 工作区
- 暂存区
- 版本库
这一篇继续往下走,重点整理 Git 中最常用的几个命令:
git addgit commitgit statusgit diffgit log
这些命令看起来简单,但它们是 Git 日常使用的核心。
尤其是git add和git commit,很多刚开始学习 Git 的人会把它们混在一起。其实这两个命令分别对应不同阶段:
git add :把修改放入暂存区 git commit :把暂存区内容提交到本地仓库理解清楚这一点,后面学习版本回退、撤销修改、分支合并时会轻松很多。
一、Git 管理的是修改,而不只是文件
刚开始学习 Git 时,很容易以为 Git 管理的是文件。
比如创建了一个ReadMe文件,然后执行:
gitaddReadMegitcommit-m"commit my first file"看起来好像 Git 管理的是这个文件本身。
但更准确地说,Git 跟踪并管理的是文件的变化,也就是“修改”。
什么是修改?
下面这些都算修改:
- 新增一个文件;
- 删除一个文件;
- 修改文件中的一行内容;
- 删除文件中的一行内容;
- 修改某几个字符;
- 新增一个空文件;
- 文件改名;
- 文件权限变化。
所以 Git 关注的不是“你有没有这个文件”,而是:
和上一次提交相比,当前工作区发生了哪些变化。
这也是为什么git status和git diff很重要。
git status用来查看当前有哪些变化;git diff用来查看具体变化内容。
二、文件在 Git 中的常见状态
一个文件在 Git 仓库中,通常会经历几个状态。
最常见的有下面几类。
1. 未跟踪状态:Untracked
新建一个文件,但还没有执行git add,这个文件就是未跟踪状态。
比如:
touchfile1gitstatus可能会看到类似提示:
这表示 Git 发现了file1,但它还没有被纳入 Git 管理。
简单来说:
文件出现在工作区,不代表它已经进入 Git 管理。
2. 已暂存状态:Staged
当执行:
gitaddfile1文件就会进入暂存区。
这时再查看状态:
gitstatus可能会看到:
这说明file1已经准备好进入下一次提交。
也就是说:
暂存区保存的是下一次git commit要提交的内容。
3. 已提交状态:Committed
执行:
gitcommit-m"add file1"暂存区中的内容就会被提交到本地仓库。
提交之后,这次修改就真正进入了版本库。
这时再执行:
gitstatus如果没有新的修改,通常会看到:
nothing to commit, working tree clean这表示当前工作区是干净的,没有需要提交的新变化。
4. 已修改但未暂存:Modified
如果一个已经被 Git 管理的文件发生了修改,但还没有执行git add,它就是已修改但未暂存状态。
比如修改ReadMe文件后执行:
gitstatus可能会看到:
Changes not stagedforcommit:(use"git add <file>..."to update what will be committed)modified: ReadMe这表示 Git 知道ReadMe被修改了,但这个修改还没有加入暂存区。
三、git add 和 git commit:暂存与提交
git add和git commit是 Git 本地操作中最核心的一组命令。
1. git add 的作用
git add的作用是:
把工作区中的修改添加到暂存区。
常见用法有几种。
添加单个文件:
gitaddReadMe添加多个文件:
gitaddfile1 file2 file3添加某个目录:
gitaddsrc添加当前目录下所有修改:
gitadd.git add .很方便,但也容易把不想提交的文件一起加进去。
所以在执行git add .前,最好先看一下:
gitstatus确认当前有哪些文件发生了变化。
2. git commit 的作用
git commit的作用是:
把暂存区中的内容提交到本地仓库。
最常见用法是:
gitcommit-m"提交说明"比如:
gitcommit-m"add ReadMe file"这里的-m后面是提交说明。
提交说明不是给 Git 看的,而是给人看的。
以后查看历史记录时,我们需要通过提交说明快速判断某次提交做了什么。
不推荐这样写:
gitcommit-m"update"因为update太笼统了,过几天再看根本不知道更新了什么。
更推荐写成:
gitcommit-m"add user login page"gitcommit-m"fix login error message"gitcommit-m"add order query api"好的提交说明应该尽量做到:
- 简洁;
- 明确;
- 能说明本次提交的目的。
3. 多次 git add 可以对应一次 git commit
暂存区的一个重要作用是:
可以先收集多次修改,然后一次性提交。
比如创建三个文件:
touchfile1 file2 file3分别加入暂存区:
gitaddfile1gitaddfile2gitaddfile3最后一次性提交:
gitcommit-m"add three files"提交成功后,可能会看到类似输出:
[master 23807c5]addthree files3files changed,0insertions(+),0deletions(-)create mode100644file1 create mode100644file2 create mode100644file3这说明三个文件被作为同一次提交保存到了版本库中。
这里要重点理解:
git commit提交的是暂存区里的内容,而不是工作区里的全部内容。
四、一个容易踩坑的例子:只提交了 add 过的文件
来看一个很容易踩坑的例子。
先创建file4:
touchfile4把file4加入暂存区:
gitaddfile4然后再创建一个file5:
touchfile5此时直接提交:
gitcommit-m"add file"提交成功后可能会看到:
[master 3d406c0]addfile1filechanged,0insertions(+),0deletions(-)create mode100644file4这里明明创建了两个文件:file4和file5,为什么只提交了一个文件?
原因很简单:
只有file4执行过git add,进入了暂存区;file5只是存在于工作区,并没有进入暂存区。
所以这次git commit只提交了file4。
如果要提交file5,还需要继续执行:
gitaddfile5gitcommit-m"add file5"这个例子非常重要,因为它说明:
Git 不会因为文件存在于工作区,就自动把它提交到版本库。
五、git status:看懂当前仓库状态
git status是日常使用 Git 时非常重要的命令。
它的作用是:
查看当前工作区和暂存区的状态。
比如修改ReadMe文件后执行:
gitstatus可能会看到:
On branch master Changes not stagedforcommit:(use"git add <file>..."to update what will be committed)(use"git restore <file>..."to discard changesinworking directory)modified: ReadMe no changes added to commit(use"git add"and/or"git commit -a")这段信息可以这样理解:
On branch master表示当前位于master分支。
Changes not staged for commit表示有修改还没有加入暂存区。
modified: ReadMe表示ReadMe文件被修改了。
no changes added to commit表示当前暂存区没有可提交内容。
所以这时如果想提交ReadMe的修改,需要先执行:
gitaddReadMe然后再提交:
gitcommit-m"modify ReadMe"常见状态总结
如果看到:
nothing to commit, working tree clean表示当前没有需要提交的修改。
如果看到:
Untracked files: file1表示file1是新文件,但还没有被 Git 跟踪。
如果看到:
Changes not stagedforcommit: modified: ReadMe表示文件已修改,但还没有加入暂存区。
如果看到:
Changes to be committed: modified: ReadMe表示修改已经进入暂存区,可以提交。
六、git diff:查看文件具体改了什么
git status只能告诉我们哪些文件发生了变化,但不能告诉我们具体改了哪里。
如果想查看具体修改内容,就需要使用:
gitdiff或者查看某个文件:
gitdiffReadMe注意:git diff 只能对比已经被 git
跟踪的文件的修改。对于全新的文件,git
没有旧版本可以对比,所以 git diff 什么都不显示。
解决方法: 先把文件添加到 git 跟踪
之后你再修改 ReadMe,git diff 就能看到变化了。
假设ReadMe原来是:
hello bit hello bit现在修改成:
hello bit hello git hello world执行:
gitdiffReadMe可能会看到类似输出:
diff --git a/ReadMe b/ReadMe index 9c9e1f0..4a97140 100644 --- a/ReadMe +++ b/ReadMe @@ -1,2 +1,3 @@ hello bit -hello bit +hello git +hello world这段 diff 信息可以这样理解:
-hello bit前面带-,表示这一行被删除了。
+hello git +hello world前面带+,表示这些行是新增的。
所以这次修改可以总结为:
- 删除了一行
hello bit; - 新增了一行
hello git; - 新增了一行
hello world。
git status 和 git diff 的区别
它们的区别可以简单理解为:
git status:告诉你哪些文件变了 git diff :告诉你文件具体怎么变了真实开发中,一般建议提交前至少执行一次:
gitstatus如果发现有修改,再执行:
gitdiff确认修改内容没问题后,再提交:
gitadd.gitcommit-m"xxx"提交前检查差异,可以避免把调试代码、临时文件、错误修改一起提交进去。
git diff HEAD – file
除了普通的git diff,还有一种常见写法:
gitdiffHEAD -- ReadMe它表示:
查看当前工作区文件和版本库中最新版本之间的差异。
这里先简单理解:
HEAD可以先理解为当前版本;ReadMe是要比较的文件;--用来分隔版本和文件路径,避免歧义。
刚开始不需要把所有细节都记死,可以先记住:
如果想看某个文件相对于当前版本到底改了什么,可以用git diff HEAD -- 文件名。
七、修改文件后的推荐提交流程
修改文件后,比较推荐的完整提交流程是:
gitstatusgitdiffReadMegitaddReadMegitstatusgitcommit-m"modify ReadMe file"gitstatus逐步来看:
1. 查看当前状态
gitstatus确认有哪些文件发生变化。
2. 查看具体差异
gitdiffReadMe确认这次修改是否符合预期。
3. 加入暂存区
gitaddReadMe把修改加入暂存区。
4. 再次查看状态
gitstatus此时可能会看到:
Changes to be committed:(use"git restore --staged <file>..."to unstage)modified: ReadMe表示修改已经进入暂存区,等待提交。
5. 提交到本地仓库
gitcommit-m"modify ReadMe file"提交成功后可能会看到:
[master 94da695]modify ReadMefile1filechanged,2insertions(+),1deletion(-)这表示:
- 1 个文件发生变化;
- 新增了 2 行;
- 删除了 1 行。
6. 确认工作区干净
gitstatus如果看到:
nothing to commit, working tree clean说明当前没有待提交修改。
八、git log 和 commit id:查看提交历史
提交完成后,可以使用:
gitlog查看历史提交。
示例:
commit 23807c536969cd886c4fb624b997ca575756eed6(HEAD ->master)Author: zhangsan<zhangsan@example.com>Date: Sat May611:27:322023+0800addthree files commit c61428926f3853d4ec6dde904415b0e6c1dabcc6 Author: zhangsan<zhangsan@example.com>Date: Sat May611:25:502023+0800 commit my firstfilegit log默认会按时间从近到远显示提交历史。
每条提交通常包含:
commit id- 作者
- 提交时间
- 提交说明
其中commit id是 Git 为每次提交生成的唯一标识。
它不是简单的1、2、3递增数字,而是一个很长的哈希值。
比如:
23807c536969cd886c4fb624b997ca575756eed6后面做版本回退时,经常会用到commit id。
如果觉得git log输出太长,可以使用:
gitlog--pretty=oneline输出会更加简洁:
commit id 为什么不是简单数字
Git 的commit id是通过哈希算法计算出来的一个非常大的十六进制数字。
这么设计有几个好处:
- 几乎可以保证每次提交的 ID 唯一;
- 适合分布式协作场景;
- 不依赖一个中心服务器来分配版本号;
- 可以通过内容生成标识。
现在不需要深入哈希算法,只要先知道:
每次提交都会生成一个唯一的 commit id,它是后面定位历史版本的重要依据。
九、从 .git 目录理解 add、commit 和对象存储
如果只停留在命令层面,Git 可能会显得有点抽象。
其实可以通过.git目录理解这些命令背后的变化。
本地仓库中有几个比较关键的文件或目录:
.git/ ├── HEAD ├── index ├── objects └── refs/ └── heads/ └── master1. index:暂存区
.git/index可以简单理解为暂存区。
当执行:
gitaddReadMe暂存区会被更新。
也就是说:
git add会影响.git/index。
2. HEAD:当前分支指针
查看.git/HEAD:
cat.git/HEAD可能会看到:
ref: refs/heads/master这表示当前HEAD指向master分支。
现在不需要深入分支,只要先理解:
HEAD 和当前所在分支、当前版本有关。
3. refs/heads/master:当前分支最新提交
查看:
cat.git/refs/heads/master可能会看到:
23807c536969cd886c4fb624b997ca575756eed6这表示master分支当前指向这个提交 ID。
4. objects:Git 对象库
.git/objects保存 Git 的对象数据。
提交、目录树、文件内容,最终都会以对象形式保存在这里。
可以简单理解为:
objects 目录保存了 Git 维护版本历史所需的底层数据。
5. commit、tree、blob 对象
Git 对象一般不能直接打开看,因为它们是经过 Git 特殊方式存储的。
不过可以使用:
gitcat-file-pcommit_id查看某个对象内容。
比如:
gitcat-file-p23807c536969cd886c4fb624b997ca575756eed6可能会看到:
tree 830a8c9feefbdc098bbae2cdc25e5034ce1920d7 parent c61428926f3853d4ec6dde904415b0e6c1dabcc6 author zhangsan<zhangsan@example.com>1683343652+0800 committer zhangsan<zhangsan@example.com>1683343652+0800addthree files这里可以看到几个信息:
tree:这次提交对应的目录树对象;parent:上一次提交;author:作者;committer:提交者;- 最后一行是提交说明。
继续查看tree对象:
gitcat-file-p830a8c9feefbdc098bbae2cdc25e5034ce1920d7可能会看到:
100644blob 9c9e1f0f6bff3015df71a0963004476f5e6cfd54 ReadMe100644blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file1100644blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file2100644blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 file3这里的blob可以理解为文件内容对象。
继续查看ReadMe对应的blob:
gitcat-file-p9c9e1f0f6bff3015df71a0963004476f5e6cfd54可能会看到:
hello bit hello bit这说明 Git 确实把文件内容记录到了对象库中。
入门阶段不需要把 Git 对象模型完全吃透,但这个例子能帮助我们建立一个重要认识:
Git 的提交并不是简单复制文件,而是通过 commit、tree、blob 等对象组织项目快照。
总结
这一篇主要整理了 Git 文件状态管理相关内容。
重点包括:
- Git 管理的是修改,而不仅仅是文件;
- 文件在 Git 中常见的几种状态;
git add的作用;git commit的作用;- 多次
git add可以对应一次git commit; - 没有进入暂存区的文件不会被提交;
git status如何查看仓库状态;git diff如何查看具体差异;- 修改文件后的推荐提交流程;
git log如何查看历史提交;commit id的作用;.git/index、.git/HEAD、.git/refs、.git/objects的基本作用;commit、tree、blob对象之间的关系。
总结:
先用 git status 看状态,再用 git diff 看具体修改,确认无误后 git add,最后 git commit。这个流程是以后每天都会用到的基础操作。
下一篇会继续整理 Git 中非常重要的能力:版本回退与撤销修改。
重点会包括:
git reset是什么;--soft、--mixed、--hard有什么区别;HEAD、HEAD^、HEAD~n怎么理解;- 如何回退到历史版本;
- 如何撤销工作区和暂存区的修改;
- 删除文件后如何恢复。
