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 的原理与使用(中)
五、分支管理
六、远程操作