Git 学习(四)操作修改和版本穿梭

时间:2023-03-09 12:52:22
Git 学习(四)操作修改和版本穿梭

Git 学习(四)操作修改和版本穿梭

  

  之前的章节,已介绍了本地Git库创建、暂存区增、删、改,以及提交版本库;可回顾下命令操作: git add 和 git commit。

  光有之前章节的操作,Git 显然不能满足版本控制的需求。所谓的版本控制,可理解为文件夹的时间机,即从创建该文件夹伊始,所有文件提交操作都将被记录版本库,且可以随意穿梭版本(回退至昨日的版本,或甚至N年前)。

  本文就此具体说明Git是如何管理修改、撤销修改以及在各个版本间穿梭的。

  管理修改

    为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。为达成这一目的,暂存区存在的意义就在于此。

    此外,先依据 .git/index (暂存区文件)中记录的时间戳、长度等信息判断工作区文件是否改变。如果工作区的文件时间戳改变,说明文件的内容可能被改变了,需要要打开文件,读取文件内容,和更改前的原始文件相比较,判断文件内容是否被更改。如果文件内容没有改变,则将该文件新的时间戳记录到 .git/index 文件中。因为判断文件是否更改,使用时间戳、文件长度等信息进行比较要比通过文件内容比较要快的多,所以 Git 这样的实现方式可以让工作区状态扫描更快速的执行,这也是 Git 高效的因素之一。

    git/index 实际上就是一个包含文件索引的目录树,像是一个虚拟的工作区。在这个虚拟工作区的目录树中,记录了文件名、文件的状态信息(时间戳、文件长度等),文件的内容并不存储其中,而是保存在 Git 对象库(.git/objects)中,文件索引建立了文件和对象库中对象实体之间的对应。下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系。

Git 学习(四)操作修改和版本穿梭

工作区、版本库、暂存区 操作原理图

在这个图中,我们可以看到部分 Git 命令是如何影响工作区和暂存区(stage/index)的:

  图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/ndex),标记为 "master" 的是 master 分支所代表的目录树

  图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个“游标”。所以图示的命令中出现 HEAD 的地方可以用 master 来替换

  图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下

  当对工作区修改(或新增)的文件执行 "git add ..." 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID 被记录在暂存区的文件索引中。

  当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树

  当执行 "git reset HEAD" 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响

  当执行 "git rm --cached <file>" 命令时,会直接从暂存区删除文件,工作区则不做出改变

  当执行 "git checkout -- <file>" 命令时,会将文件在工作区的修改撤销

  当执行 "git checkout HEAD ." 或者 "git checkout HEAD <file>" 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。

上文内容其实是第三章: Git 学习(三)本地仓库操作——git add & commit 工作区与版本库的补充说明,原理图中提及的部分命令还未介绍,可先大致浏览,完全看完下文后再理解上图(该图很重要)。

需要注意:工作区其实与Git库是分离的,我们在工作区进行的修改,如果不add到暂存区,commit提交并不会提交版本(再次重申,commit 提交修改仅针对暂存区)。

这边所说的修改,包括文件本身的修改(删行、加行、改内容等),而创建新文件或删除,也算一个修改。

 

  修改撤销

    这边,介绍下暂存区撤销修改的命令:

      git checkout -- <file1> <file2> ...   将文件在工作区的修改全部撤销,可多个,空格分隔

git checkout -- file命令中的 "--" 很重要,没有"--",就变成了切换分支的命令,切换分支命令将在以后章节介绍。

    举几个具体示例来说明以上命令的用法,初始化空git库,若你先创建了 1.txt 并将其 git add,你在工作区修改了 1.txt 文件,但发现修改错了,需要撤销修改至当时 git add 的情况

      Git 学习(四)操作修改和版本穿梭

       git status  可见如下提示

      Git 学习(四)操作修改和版本穿梭

       git checkout -- 1.txt  后,再次打开 1.txt,发现其中的内容被撤销修改了,即内容为 111;

      若工作区删除了 1.txt,但现在又想撤销删除,也可操作 git checkout -- 1.txt ,此时查看工作区,可发现 1.txt 被恢复了。

      Git 学习(四)操作修改和版本穿梭

  版本穿梭

    仅仅针对工作区的修改撤销是远远不够的,需要在所有版本库中任意切换;版本是针对 commit 操作而言的,每次 commit 成功后,都会自动生成版本号。

      git log   显示当前分支提交版本库的日志

      Git 学习(四)操作修改和版本穿梭

       git log  该命令很常用,可显示提交日志信息(当前版本库,可加参数,help查询具体);上图可见茶色字体显示了一大串类似Git 学习(四)操作修改和版本穿梭

      每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:

      Git 学习(四)操作修改和版本穿梭

      git reset --hard <revision>  重置至某一版本(强制,暂存区和工作区均重置)

        必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^ ...
        如上,我们要把当前版本回退到最新版本,就可以使用命令行  git reset --hard ,若使用命令行  git reset --hard HEAD^,则回滚为上一版本,此时  git log  仅显示了当前版本及其之前的信息

        Git 学习(四)操作修改和版本穿梭

        命令可输入版本号,前几位即可(通常前7位);显然,若回滚了昨天的版本后,又反悔了的这种情况还是时有发生的,这时,就需要输版本号了;然则,版本号忘了怎么办。。这时,需要另一个命令帮助:

      git reflog  显示操作的日志

        该命令可显示操作日志,且显示了对应的版本号及信息,可查询到之前的版本号并再次回滚, 如  git reset --hard 6de39b3  。

      Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD指向改变

      Git 学习(四)操作修改和版本穿梭

      改为指向 2

      Git 学习(四)操作修改和版本穿梭

      然后顺便把工作区、暂存区文件更新了。