Git 的原理与使用(上) (二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 如果直接将某个文件拷贝到 .git 文件的同级目录gitcode下,此时这个文件是不会被Git管理的。

Git 的原理与使用(上) (一)+ https://developer.aliyun.com/article/1521932?spm=a2c6h.13148508.setting.14.439a4f0ewmJqMA


四、认识工作区、暂存区、版本库


如果直接将某个文件拷贝到 .git 文件的同级目录gitcode下,此时这个文件是不会被Git管理的。


事实上,.git 目录才是真正的Git版本库(即代码仓库)。


那么,是不是直接把文件拷贝到 .git 目录内就可以了呢?




答案是不行的,而且是万万不能这么做的!不允许在 .git 下手动进行任何修改,否则很可能导致整个本地仓库报废。我们自定义的文件,只能写在 gitcode 这个目录下。


而gitcode文件夹就是Git的工作区。


(.git 文件虽然也被包括在 gitcode 文件夹中,但它并不属于工作区,而是版本库。)


工作区:是在电脑上你要写代码或文件的目录。


暂存区:英文叫stage或index。一般存放在目录下的index文件(.git/index)中,我们把暂存区有时也叫作索引(index)。


版本库:又名仓库,英文名repository 。工作区有一个隐藏目录.git,它不算工作区,而是Git的版本库。这个版本库里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。


本地仓库:local repo ,git commit 命令就是把暂存区内的修改提交到本地仓库中。


下面这个图展示了工作区、暂存区、版本库之间的关系:




图中左侧为工作区,右侧为版本库。Git的版本库里存了很多东西,其中最重要的就是暂存区(stage)。


在创建Git版本库时,Git会为我们自动创建一个唯一的master分支(),以及指向master的一个指针叫HEAD。(分支和HEAD的概念后面再说)


当对工作区修改(或新增)的文件执行git add命令时,暂存区目录树的文件索引会被更新。


当执行提交操作 git commit 时,master分支会做相应的更新,可以简单理解为暂存区的目录树才会被真正写到版本库中。【master是本地仓库中的主要分支,可以理解为提交到master中,才是提交x真正到了本地仓库。】


由上述描述我们便能得知:通过新建或粘贴进目录的文件,并不能称之为向仓库中新增文件,而只是在工作区新增了文件。必须要通过使用git add和 git commit命令才能将文件添加到仓库中进行管理。




1.添加文件--场景一


在包含.git的目录下新建一个ReadMe文件,我们可以用git add命令将文件添加到暂存区:


添加一个或多个文件到暂存区:git add [file1] [file2] ...


添加指定目录到暂存区,包括子目录:git add [dir]


添加当前目录下的所有文件改动到暂存区:git add .


再使用git commit命令将暂存区内容添加到本地仓库中:


提交暂存区全部内容到本地仓库中:git commit -m "message"


提交暂存区的指定文件到仓库区:git commit [file1] [file2] ... -m "message"


注意,git commit后面的-m选项要跟上描述本次提交的message,由用户自己填写。这部分内容不仅绝不能省略,而且还要好好描述,message是用来记录你的提交细节的,是给其他人看的。


例如:


[wyd@VM-4-13-centos linux_gitcode]$ touch ReadMe
                                    # 在ReadMe文件中写入 hello world
[wyd@VM-4-13-centos linux_gitcode]$ echo hello world > ReadMe    
[wyd@VM-4-13-centos linux_gitcode]$ cat ReadMe
hello world
 
                                    # 将ReadMe的修改加入暂存区
[wyd@VM-4-13-centos linux_gitcode]$ git add ReadMe
                                    # 将暂存区的内容提交到仓库
[wyd@VM-4-13-centos linux_gitcode]$ git commit -m '提交第一个文件'
[master (root-commit) 1853b26] 提交第一个文件
 1 file changed, 1 insertion(+)
 create mode 100644 ReadMe

git commit命令执行成功后会告诉我们:1个文件被改动(也就是我们新添加的ReadMe文件),插入了1行内容(ReadMe有1行内容)。


我们还可以多次add不同的文件,而只commit一次便可以提交所有文件,是因为需要提交的文件是通通被add到暂存区中,然后一次性commit暂存区的所有修改。如:


[wyd@VM-4-13-centos linux_gitcode]$ touch file1 file2 file3
[wyd@VM-4-13-centos linux_gitcode]$ git add file1 file2 file3
[wyd@VM-4-13-centos linux_gitcode]$ git commit -m '一次性提交3个文件'
[master 68df79a] 一次性提交3个文件
 3 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1
 create mode 100644 file2
 create mode 100644 file3

截至目前,我们已经更够将代码直接提交至本地仓库了。


git log命令可以帮助我们查看历史提交记录:


[wyd@VM-4-13-centos linux_gitcode]$ git log
commit 68df79a6f2a582fd8f390eaa947577b898bc2069
Author: wyd <1256970054@qq.com>
Date:   Sun Dec 3 21:41:13 2023 +0800
 
    一次性提交3个文件
 
commit 1853b26b95891348de92982b34f3bdbdfa01e049
Author: wyd <1256970054@qq.com>
Date:   Sun Dec 3 21:39:19 2023 +0800
 
    提交第一个文件

该命令显示从最近到最远的提交日志,并且可以看到我们commit时的日志消息。


如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=onelne参数:


git log --pretty=onelne


我们看到的一大串数字是每次提交的 commit id(版本号),Git 的 commit id 不是1,2,3……递增的数字,而是一个 SHA1 计算出来的一个非常大的数字,用十六进制表示。


2.查看.git文件


先来查看.git目录的结构:


[wyd@VM-4-13-centos linux_gitcode]$ tree .git
.git
|-- branches
|-- COMMIT_EDITMSG
|-- config
|-- description
|-- HEAD
|-- hooks
|   |-- applypatch-msg.sample
|   |-- commit-msg.sample
|   |-- post-update.sample
|   |-- pre-applypatch.sample
|   |-- pre-commit.sample
|   |-- prepare-commit-msg.sample
|   |-- pre-push.sample
|   |-- pre-rebase.sample
|   `-- update.sample
|-- index
|-- info
|   `-- exclude
|-- logs
|   |-- HEAD
|   `-- refs
|       `-- heads
|           `-- master
|-- objects
|   |-- 18
|   |   `-- 53b26b95891348de92982b34f3bdbdfa01e049
|   |-- 25
|   |   `-- 1f5c5b7c0b8937ee454e3c1ec6a26e92951bd5
|   |-- 3b
|   |   `-- 18e512dba79e4c8300dd08aeb37f8e728b8dad
|   |-- 68
|   |   `-- df79a6f2a582fd8f390eaa947577b898bc2069
|   |-- 82
|   |   `-- a432cf4469d279b2ec65a226fbbb5c19fb56c9
|   |-- e6
|   |   `-- 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
|   |-- info
|   `-- pack
`-- refs
    |-- heads
    |   `-- master
    `-- tags
 
18 directories, 24 files


其中:


1、index就是暂存区,add后的内容都是添加到这里的。


2、HEAD就是我们的默认指向master分支的指针。


[wyd@VM-4-13-centos linux_gitcode]$ cat .git/HEAD
ref: refs/heads/master



找到默认的master分支:




其中存放的一串ID其实就是当前最新的 commit id,可以理解为一个git对象。


3、objects为Git的对象库,里面包含了创建的各种版本库对象及内容。


当执行命令git add时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,就位于".git/objects"目录下。




查找object时要将分成2部分,其前2位是文件夹名称,后38位是文件名称。


比如上面查到的 68df79a6f2a582fd8f390eaa947577b898bc2069 ,即说明它位于 68 文件夹下。


找到这个文件之后,一般不能直接看到里面是什么,该类文件是经过sha(安全哈希算法)加密过的文件,好在我们可以使用git cat-file命令来查看版本库对象的内容:



[wyd@VM-4-13-centos linux_gitcode]$ git cat-file -p 68df79a6f2a582fd8f390eaa947577b898bc2069
tree 82a432cf4469d279b2ec65a226fbbb5c19fb56c9
parent 1853b26b95891348de92982b34f3bdbdfa01e049
author wyd <xxxxxxxxxx@qq.com> 1701610873 +0800
committer wyd <xxxxxxxxxx@qq.com> 1701610873 +0800
 
一次性提交3个文件


其中,还有一行tree 82a432cf4469d279b2ec65a226fbbb5c19fb56c9,使用同样的方法查看:

[wyd@VM-4-13-centos linux_gitcode]$ git cat-file -p 82a432cf4469d279b2ec65a226fbbb5c19fb56c9
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad        ReadMe
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391        file1
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391        file2
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391        file3

再看 ReadMe 对应的 3b18e512dba79e4c8300dd08aeb37f8e728b8dad


[wyd@VM-4-13-centos linux_gitcode]$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world
 
# 这是我们对ReadMe做的修改!!被git记录了下来!!


每一次提交的修改都会被git以对象的方式记录下来。


总结一下,在本地的git仓库中,有几个文件或者目录很特殊


index:暂存区,git add后会更新该内容。


HEAD:默认指向master分支的一个指针。


refs/heads/master:文件里保存当前master分支的最新commit id(索引,对应的是git对象,对象被维护在objects中)。


objects:包含了创建的各种版本库对象及内容,可以简单理解为放了git维护的所有修改。


后面再学习过程中,最好能将常见的git操作与.git目录当中的结构内容变化对应起来,这样有利于我们理解git细节流程。


3.添加文件--场景二


学习到这里,我们已经清楚了如何向仓库中添加文件,并且对于工作区、暂存区、版本库也有了一定的认识。这里再展示一种添加文件的场景,能加深我们对工作区、暂存区、版本库的理解:

tjfz@139-159-150-152:~/gitcodes touch file4    #1.新增file4文件
tjfz@139-159-150-152:~/gitcodes git add file4    #2.将file4添加到暂存区
tjfz@139-159-150-152:~/gitcodes touch file5    #3.新增file5文件
tjfz@139-159-150-152:~/gitcodes git commit -m "add file"    #4.提交修改
[master 3d406co] add file
1 file changed, insertions(+), deletions(-)
create mode 100644 file4

提交后发现打印了 1 file changed,0 insertions(+),0 deletions(-),意思是只有一个文件改变了,这时我们提出了疑问,不是新增了两个文件吗?


再来回忆下,git add file5,file5 就不在暂存区中维护,所以我们 commit 的时候其实只是把已经在暂存区的file4提交了,而遗漏了工作区的file5。


如何提交file5呢?很简单,再次add、commit即可。


4.修改文件


Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。


什么是修改?新增、删除、更改了某些字符都是修改。


objects存储的git对象记录的就是修改的工作区中的内容。


让我们将ReadMe文件进行一次修改:


[wyd@VM-4-13-centos linux_gitcode]$ echo wonderful! > ReadMe
[wyd@VM-4-13-centos linux_gitcode]$ cat ReadMe
wonderful!

此时,仓库中的ReadMe和我们工作区的ReadMe是不同的,如何查看当前仓库的状态呢?


git status命令用于查看在上次提交之后是否有对文件进行再次修改。


[wyd@VM-4-13-centos linux_gitcode]$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#        modified:   ReadMe
#
no changes added to commit (use "git add" and/or "git commit -a")

上面的结果告诉我们,ReadMe被修改过了,但还没有完成添加与提交。


目前,我们只知道文件被修改了,如果能知道具体哪些地方被修改了,就更好了。此时可以用 git diff命令:


[wyd@VM-4-13-centos linux_gitcode]$ git diff ReadMe
diff --git a/ReadMe b/ReadMe
index 3b18e51..af3b811 100644
--- a/ReadMe
+++ b/ReadMe
@@ -1 +1 @@
-hello world
+wonderful!


git diff [file]命令用来显示暂存区工作区文件的差异,显示的格式正是Unix通用的diff格式。



再修改一下 ReadMe 文件:


[wyd@VM-4-13-centos linux_gitcode]$ cat ReadMe
wonderful!
 
wonderful1!
wonderful2!
wonderful3!


git diff 一下,对比暂存区(之前add过的版本)和工作区(刚修改的版本)的ReadMe文件区别:


[wyd@VM-4-13-centos linux_gitcode]$ git diff ReadMe
diff --git a/ReadMe b/ReadMe
index 3b18e51..6a2ca8c 100644
--- a/ReadMe
+++ b/ReadMe
@@ -1 +1,5 @@
-hello world
+wonderful!
+
+wonderful1!
+wonderful2!
+wonderful3!


也可以使用git diff HEAD -- [file]命令来查看本地仓库和工作区文件的区别。


知道了对ReadMe做了什么修改后,再把它提交到本地仓库就放心多了。


[wyd@VM-4-13-centos linux_gitcode]$ git add ReadMe
[wyd@VM-4-13-centos linux_gitcode]$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#        modified:   ReadMe
#


git add 之后,就没有看到上面 no changes added to commit (use "git add" and/or "git commit -a") 的消息了。接下来让我们继续 git commit 即可。


[wyd@VM-4-13-centos linux_gitcode]$ git commit -m '提交修改后的文 件'
[master 2d511b4] 提交修改后的文件
 1 file changed, 5 insertions(+), 1 deletion(-)

5-版本回退 reset


Git能够管理文件的历史版本,这也是版本控制器重要的能力。


如果有一天你发现之前前的工作做的出现了很大的问题,需要在某个特定的历史版本重新开始,那么这个时候,就需要版本回退的功能了。


执行git reset命令用于回退版本,可以指定退回某一次提交的版本。


“回退”本质是要将本地仓库中的内容进行回退,工作区或暂存区是否回退由命令参数决定。


git reset命令的语法格式为:git reset [--soft | --mixed | --hard] [HEAD]


--mixed 为默认选项,使用时可以不用带该参数。该参数将暂存区的内容退回为指定提交版本内容,工作区文件保持不变。


--soft 参数对于工作区和暂存区的内容都不变,只是将本地仓库回退到某个指定版本。


--hard 参数将工作区和暂存区都退回到指定版本。


切记工作区有未提交的代码时不要用这个命令,因为工作区也会回滚,你没有提交的代码就再也找不回了,所以使用该参数前一定要慎重。




HEAD参数说明:


可直接写成commit id,表示指定退回的版本


HEAD表示当前版本


HEAD^上一个版本


HEAD^^上上一个版本


以此类推……


可以使用 ~数字 表示:


HEAD~0 表示当前版本


HEAD~1 上一个版本


HEAD^2 上上一个版本


以此类推……


回退功能演示:




执行命令 git reset --hard 58d0aa3 后,可见工作区、暂存区和本地仓库的内容都回退到了第一版本的样子,HEAD指针也更改的指向,指向了第一版本的commitID58d0aa3。且 git log --pretty=oneline 打印日志可见,只剩下操作第一版本时的日志,中间添加各种file的日志也没有了。


如果后悔了,还想从第一版本58d0aa3恢复到回滚前的999cead版本呢?同样的操作,git reset --hard 999cead,工作区、暂存区和本地仓库都回滚到了原来版本999cead的样子。


可见,只有拿到某一版本的commitID,才能通过reset命令实现回滚操作。


但是上述情况下,我们能从第一版本回滚到原来版本,是因为我们在还在原来版本时就打印了一下log,知道了原来版本的commitID。如果我们没有打印过原来版本的commitID,不就不能从第一版本又回退到原来版本了呢?


也就是说,如果从一个新版本回退到旧版本,但后悔了,又想回到一个新版本,在旧版本时git log肯定是拿不到想要的新版本的commitID的,这时还有一个补救的方法:git reflog,该命令能记录到本地的每一次命令:




这样,你就可以很方便的找到你的所有操作记录了,但第一列的一串数字是啥东西?


这个也是各个版本的 commit id 的部分。没错,Git 版本回退的时候,也可以使用部分 commit id 来代表目标版本。


可往往理想很丰满,现实很骨感。在实际开发中,突然某一天,我又想回退到某个版本,可由于开发的时间长了,commit id早就找不到了,那该如何操作呢?貌似现在不可能了……


值得说的是,Git 的版本回退速度非常快,因为 Git 在内部有个指向当前分支(此处是master)的HEAD 指针, refs/heads/master文件里保存当前 master 分支的最新 commit id 。


当我们在回退版本的时候,Git 仅仅是给refs/heads/master 中更换一个存储的version(即commitID),可以简单理解成如下示意图:





6-撤销修改

如果我们在我们的工作区写了很长时间代码,越写越写不下去,觉得自己写的实在是垃圾,想恢复到上一个版本。




情况一:对于工作区的代码,还没有add

你当然可以直接删掉你目前在工作区新增的代码,像这样:


# 向ReadMe中新增一行代码
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean
hyb@139-159-150-152:~/gitcode$ vim ReadMe 
hyb@139-159-150-152:~/gitcode$ cat ReadMe 
hello bit
hello git
hello world
hello version1
hello version2
hello version3
This piece of code is like shit #新增代码
 
# 查看状态
hyb@139-159-150-152:~/gitcode$ git status
On branch master
Changes not staged for commit: 
(use "git add <file>..." to update what will be committed) 
(use "git restore <file>..." to discard changes in working directory) 
    modified: ReadMe
    
no changes added to commit (use "git add" and/or "git commit -a")
 
# 直接删除代码 恢复原来的样子
hyb@139-159-150-152:~/gitcode$ vim ReadMe 
hyb@139-159-150-152:~/gitcode$ cat ReadMe 
hello bit
hello git
hello world
hello version1
hello version2
hello version3
hyb@139-159-150-152:~/gitcode$ git status
On branch master
nothing to commit, working tree clean


辛亏我们工作效率不高,才写了一行代码就发现不行了,要是你写了3天,一直都没有提交,该怎么删掉呢?你自己都忘了自己新增过哪些。


有同学说,我可以 git diff xxx 一下,看看差别在删啊,那你肯定又要花3天时间删代码了,并且很大的概率还会改出bug。一周过去了,你怎么向你的老板交代呢?


Git 其实还为我们提供了更好的方式,我们可以使用git checkout -- [file]命令让工作区的文件回到最近一次 add 或 commit 时的状态。


要注意 git checkout -- [file] 命令中的--很重要,切记不要省略,一旦省略,该命令就变为其他意思了,后面我们再说。示例如下:




情况二:已经add,但没有commit

如果已经给代码执行了add,保存到了暂存区,该怎么撤销呢?




让我们来回忆一下学过的 reset 回退命令:




该命令如果使用 --mixed 参数,可以将暂存区的内容退回为指定版本的本地仓库的内容,但工作区文件保持不变。这就达成了回退暂存区的效果,并且把当前的情况二转换成了上面的情况一,此时再要回退工作区使工作区的内容和暂存区一致,使用 git checkout -- ReadMe 命令即可。(当然,要完成工作区和暂存区的同时回退,也可以直接使用 --hard 选项,这样只需要一行代码,这里不做演示。)


示例如下:



意:git reset HEAD 就是回到当前版本的意思(如果要回到上一版本,写作HEAD^)

git status查看一下,发现现在暂存区是干净的,只有工作区有修改。



丢弃工作区的修改:




恢复了!


情况三:已经add,并且也commit了

不要担心,我们可以 git reset --hard HEAD^回退到上一个版本。


--hard:同时将本地仓库、工作区、暂存区都回退到上一版本。


HEAD^:上一版本的写法。


不过,前提条件是你还没有把自己的本地仓库push到远程仓库。


事实上,撤销的目的就是让本地仓库的代码不影响远程仓库的代码。


还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你推送到远程版本库,你就真的惨了……



准备工作:add、commit



回退操作:将本地仓库内容回退到上一版本,且同步至工作区、暂存区。




7-删除本地仓库中的文件

在Git中,删除也是一个修改操作。

如果要删除本地仓库中的文件,怎么操作呢?

方法一:先在工作区中正常 rm file5,然后把工作区删除操作add file5到暂存区、commit到本地仓库。




在工作区中的修改(修改也包括删除操作)add到暂存区中后,暂存区会提示 file5 被删除,再 commit的时候这个删除的操作和结果也会被同步到本地仓库。

方法二:git rm file 命令直接将 file 从工作区和暂存区中一同删除,然后直接 commit 到本地仓库。





**小结

本篇涉及的部分git命令:


git init:初始化git仓库


git config:配置git配置项


git config -l:查看配置项


git config --unset:删除配置项


git add:添加工作区的内容到暂存区


git commit:提交暂存区中的内容到版本库


git log:查看历史提交记录


git cat-file:查看版本库对象的内容


git status:查看仓库状态


git diff:显示文件差异


git reset:版本回退


**下篇内容


Git 的原理与使用(中)


五、分支管理


六、远程操作

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
Linux 网络安全 开发工具
1.Git使用技巧-基础原理
1.Git使用技巧-基础原理
65 0
|
Linux 网络安全 开发工具
【Git】Git 原理和使用
【Git】Git 原理和使用
418 4
|
6月前
|
存储 开发工具 git
Git的基本操作和原理
Git的基本操作和原理
|
3月前
|
存储 开发工具 数据库
Git的工作原理是什么
【8月更文挑战第24天】Git的工作原理是什么
51 0
|
5月前
|
前端开发 持续交付 开发工具
详细介绍Git的基本原理、在前端开发中的应用以及如何使用Git来优化团队协作
【6月更文挑战第14天】Git是前端开发中的必备工具,它通过分布式版本控制管理代码历史,支持分支、合并和冲突解决,促进团队协作。在前端开发中,Git用于代码追踪、版本控制、代码审查和持续集成部署,优化团队协作。制定分支策略、编写清晰提交信息、定期合并清理分支以及使用Git钩子和自动化工具能进一步提升效率。理解并善用Git,能有效提升前端项目的质量和开发效率。
77 3
|
6月前
|
运维 测试技术 开发工具
Git 的原理与使用(下)(二)
新特性或新功能开发完成后,开发人员需合到 develop 分支。
56 2
|
6月前
|
Java 网络安全 开发工具
Git 的原理与使用(中)(三)
别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
44 1
|
6月前
|
存储 安全 开发工具
Git 的原理与使用(中)(二)
Fast Forward 模式(ff模式) 通常合并分支时,如果可以,Git 会采用 Fast forward 模式。
39 1
|
6月前
|
安全 Java 开发工具
Git 的原理与使用(中)(一)
分支是Git的杀手级功能之一。
49 1
|
6月前
|
Linux 开发工具 git
Git 的原理与使用(下)(一)
在完成origin/dev分支合并到origin/master分支的操作后,origin/dev分支对于我们来说就没用了,那么dev分支就可以被删除掉。
61 0