[Git][基本原理与命令]

Git是工作中最常用的版本控制工具,本文中将介绍其常用的命令。
根据作用的不同,可以分为基本命令、撤销命令、合并命令与远程仓库命令,下面将依次介绍这些命令。

git 中提供了底层api供我们直接对底层数据结构进行调用,其中 git cat-file [-t] [-p] hashcode,其中 -t可以查看hash值对应文件的类型, -p可以查看hash之对应的内容

基本对象

在最初的版本里实际上只有3种对象,分别是blob,tree和changeset(后续被改名为commit).本文主要对这三种对象进行介绍。基本的对象有tag域与binary data域。

在blob对象中,其tag的取值为blob,并在binary data域中存储了文件的内容,是最简单的object。但并不会保存文件名相关的信息。那么在一个工程项目中,我们有时候仅会对文件的名字进行改动,这时候则需要引出tree对象对这一变化进行追踪。

tree的tag值为tree,binary data中维护了一个list。list的每一个item可以是一个tree对象,也可以是一个blob对象。 在每一个item中,有如下的属性:

  • 权限和类型
  • 路径
  • 对象的sha1(类似于指针)
    从这个角度来看,tree的list中既可以引用blob对象,也可以引用tree对象,循环往复,构成一颗文件结构树。
    [Git][基本原理与命令]

我们知道,若文件内容发生变化,则会生成新的blob对象与新的sha1值。对于tree对象,我们使用sha1值作为对blob对象的引用,那么该目录中的文件内容发生变化而引起对应的blob对象发生变化是,会导致tree维护的list中的内容发生变化,那么tree本身的sha1值也会发生变化。这就意味着,我们需要生成一个新的tree对象,来维护这一变化。

对于如下结构的目录:
.
├── foo
│ └── a.txt
└── b.txt
如果我们将a.txt中的内容从”111″修改为”222″, 那么整体的tree将会有如下结构:

[Git][基本原理与命令]
可以看到新内容与旧内容分别维护了各自的根目录对应的tree,并复用没有修改内容的blob节点。

仅靠tree对象与blob对象并不能记录与提交版本相关的信息。为了回溯任意一个版本,我们还需要一个对象对版本的提交信息进行维护——commit对象。
commit对象的tag是commit,binaray data域有以下内容:

  • tree相关的信息
  • parent
  • author
  • committer
    其中,当我们提交一个新的commit时,会有一个对应的tree对象的与之关联。parent的个数可以是0个或者多个,维护着到上一个commit的sha1指针。可以通过该指针回溯到任意一个历史版本。从这个角度看,我们可以将git理解为基于一个key-value的数据库与默克尔树形成的有向无环图(DAG)的存储系统[3]。

tag代表将分支永久化,生成tag后,这个tag对应的内容将永不可变,所以一般应用或者软件版本的发布一般用tag。

索引区

在我们的tree结构中,如果要访问一个具体的blob对象,需要使用深度优先遍历,通过路径上的信息来得到最终blob的完整路径。如果我们想要对比不同commit版本的文件之间的差异的话,就需要对两棵不同版本的tree进行深度遍历,当树很深时会涉及大量磁盘i/o。
为了提高i/o的速度,可以引入一个缓存,来提高文件读取的速度,这就是暂存区。通常相关的数据存储在./git/index。git使用mmap技术,访问文件内容。这里的文件内容指的是一系列entry对象,每一个entry对象中维护了对应的blob对象的映射与完整路径与其他相关的信息,entry对象在内存中按完整路径进行升序排列。这样,在后续的比较中就可以使用二分查找快速定位到相同文件名的不同版本完整地址,并进行比较。

1. 基本命令

当输入git add命令后,git会将文件生成对应的blob文件,并保存在git仓库中。但是并没有在tree上进行相应的结构映射,而只是在索引区建立了直接的索引。而tree结构的建立,需要等到commit命令才执行。

git commit后就会生成本次版本的对应树结构,涉及到一系列tree对象与commit对象的生成。(注意,blob对象在git add时就已经生成)

该命令根据参数的不同,有许多效果。其主要的作用是在不同的分支之间切换。

查看代码[4]

git checkout # 列出本地所有分支
git checkout [<branch>] # &#x5207;&#x6362;&#x5230;&#x8BE5;&#x5206;&#x652F;
git checkout -b [<branch>] # &#x521B;&#x5EFA;&#x5E76;&#x5207;&#x6362;&#x5230;&#x8BE5;&#x5206;&#x652F;
git checkout -d [<branch>] # &#x5220;&#x9664;&#x8BE5;&#x5206;&#x652F;
-f # &#x5F3A;&#x5236;&#x6267;&#x884C;
</branch></branch></branch>

其余的一些命令参数如–merge,可以通过与其他命令的组合来实现,对结果把控更可靠。

2. 撤销命令

这条命令指定退回到某一个版本的提交。

查看代码

--mixed # &#x5DE5;&#x4F5C;&#x533A;&#x4E0D;&#x53D8;&#xFF0C;&#x5176;&#x4F59;&#x5185;&#x5BB9;&#x9000;&#x56DE;&#x5230;&#x6307;&#x5B9A;&#x7248;&#x672C;
-hard # &#x6240;&#x6709;&#x5185;&#x5BB9;&#x9000;&#x56DE;&#x5230;&#x6307;&#x5B9A;&#x7248;&#x672C;
-soft # &#x56DE;&#x9000;&#x5230;&#x6307;&#x5B9A;&#x7248;&#x672C;
&#x8BE5;&#x547D;&#x4EE4;&#x540E;&#x53EF;&#x4EE5;&#x6307;&#x5B9A;&#x6587;&#x4EF6;&#x540D;&#xFF0C;&#x7528;&#x4E8E;&#x5BF9;&#x6307;&#x5B9A;&#x6587;&#x4EF6;&#x8FDB;&#x884C;&#x7248;&#x672C;&#x56DE;&#x9000;
&#x9700;&#x8981;&#x8C28;&#x614E;&#x4F7F;&#x7528; --hard&#x53C2;&#x6570;&#xFF0C;&#x8BE5;&#x53C2;&#x6570;&#x4F1A;&#x5220;&#x9664;&#x56DE;&#x9000;&#x7248;&#x672C;&#x540E;&#x7684;commit&#x7248;&#x672C;&#x3002;&#x53E6;&#x5916;&#x6C38;&#x8FDC;&#x4E0D;&#x8981;&#x5C06;&#x5DF2;&#x7ECF;push&#x5E76;merge&#x5230;origin&#x4E2D;&#x7684;branch&#x7684;&#x5185;&#x5BB9;&#x8FDB;&#x884C;&#x56DE;&#x9000;&#xFF0C;&#x5426;&#x5219;&#x4F1A;&#x5BFC;&#x81F4;&#x672C;&#x5730;&#x4ED3;&#x5E93;&#x7684;&#x4E0D;&#x540C;&#x6B65;&#x3002;&#x5982;&#x679C;&#x975E;&#x8981;&#x4F7F;&#x7528;&#xFF0C;&#x540E;&#x7EED;&#x5219;&#x9700;&#x8981;git push -f&#x8FDB;&#x884C;&#x5F3A;&#x5236;&#x63A8;&#x9001;

其用法与 git reset类似,但是与reset不同的是,其不会删除一些历史commit记录,而是使用创建一个新的commit的方式来达到撤销历史修改的记录。对于一些已经push或产生其他副作用的修改,最好使用revert方式进行撤销操作。而reset则多用于本地的撤销修改。注:revert的commitid选则回退版本后一个的commitid。

3. 合并命令

会在两个parent commit上创建一个新的分支,若不存在冲突,则直接fast-forward merge。

查看代码

git merge [<branch>] # &#x5C06;&#x6307;&#x5B9A;&#x7684;&#x5206;&#x652F;&#x5408;&#x5E76;&#x5230;&#x5F53;&#x524D;&#x5206;&#x652F;&#x4E0A;
</branch>

若合并产生冲突,会在日志中提示,并且在合并操作产生的commit中,会标出冲突的部位。当我们以手动的方式修改完冲突后,使用git add 通知git已完成冲突合并,并用commit完成这次merge(此时会自动带上 merge的message)

从字面上来理解,是为当前分支重新确定一个基。达到的效果与merge类似,但是其commit的拓扑结构是不一致的。当执行完rebase后,是将当前分支以”类似”嫁接的形式,嫁接到某个指定的分支上,而不是类似于merge,将两个分支合并起来。

当前我们的分支结构如下:

[Git][基本原理与命令]
我们正在dev分支上,想将dev的修改记录应用到master分支上,但又不想以merge的方式进行。此时我们可以使用rebase,为dev分支重新确定一个基。执行完 git rebase master后,效果如下:
[Git][基本原理与命令]
完成了类似嫁接的操作, 将dev的原始分叉点重新定位在master分支的最新的commit上,并生成了与dev分支长度相同的新的一系列commit。

4. 远程仓库命令

该命令会在本地仓库中更新所有在远程端被追踪的分支,并用灰色的标记来标记这些属于remote的分支

远程与本地分支的原始状态

[Git][基本原理与命令]
执行完 git fetch后本地仓库的状态:
[Git][基本原理与命令]

该命令相当于一条组合命令,在默认的参数下相当于 git fetch + git merge [<remote_branch>]</remote_branch>,当然我们可以指定参数 git>],当然我们可以指定参数 git pull --rebase,那么就会使用 git rebase来替代第二条命令。注意,默认只会将当前分支对应的远程分支合并进来。

原始状态

[Git][基本原理与命令]
执行完 git pull
[Git][基本原理与命令]

该命令会将本地分支的内容推送到对应的远程分支上。从理论上来讲,所有的push操作都应该进行fast-forward merge,若仍存在一些不同则会发生reject错误。遇到这种情况时,通常需要先将远程的内容pull到本地进行合并后,再进行push。

Original: https://www.cnblogs.com/xy1997/p/16436278.html
Author: xingyuanyuan
Title: [Git][基本原理与命令]

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/588102/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球