# Git基本原理探究

# 基本流程与概念

我们上一节建立了一个仓库,现在我们再来仔细探讨下它的基本流程。

ThreeState

  1. 在工作目录中修改文件。
  2. 暂存文件,将文件快照放入暂存区域。
  3. 提交更新,找到暂存区域中的文件,将快照永久性存储到Git仓库目录。

# 工作区域

GitWorkZone

从上图中我们看到Git有这样几个基本区域:

  • Working Directory:工作目录,对项目的某个版本独立提取出来的内容,这些从仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
  • State/Index:暂存区,在提交前,临时暂存工作目录中的变化,事实上它只是一个文件,保存即将提交的文件列表信息。
  • History:历史版本,也是本地的仓库,安全存放数据的位置,Git用来保存项目的元数据和对象数据库的地方。其中HEAD指向最新放入仓库的版本。
  • Remote Repository:远程仓库,在托管代码的服务器上,比如github,gitlab,gitee等。

# 文件状态

工作目录下每个文件有两种状态:已跟踪或未跟踪。已跟踪的文件是那些被纳入了版本控制的文件,在上一次快照中有它们的记录,它们可再细分成三种状态,已提交(committed),已修改(modified)和已暂存(staged)。除了已跟踪文件以外的所有其他文件都属于未跟踪文件,它们属于没有被纳入Git的版本控制系统中的文件。

  • 已修改,表示修改了文件,但还没保存到数据库中。
  • 已暂存,表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
  • 已提交,表示数据已经安全的保存在本地数据库中。

注意如果不想暂存直接提交的话可以使用git commit -am'Message'。

# git本地仓库内容

前面我们使用git init建立一个本地仓库后,会发现在该仓库目录下有一个.git文件夹,这个文件存放着所有git仓库的信息,下面我们来熟悉下这里面的内容。

# HEAD文件

该文件存放当前分支的引用,修改这个文件的跟使用切换分支命令的效果是一样的。

# config文件

存放当前仓库的配置,比如我们之前设置的email和name都是存放在这。

# refs文件夹

这个目录下面有两个文件夹,也就是tags和heads,tags里面是标签,有的书里面称为里程碑,在某个commit上创建分支时,会在这个commit上自动打上tag;heads里面是分支,每个分支是一个独立的开发空间。在这个文件夹里,会有以分支名为名的文件,每个文件内包含最近commit的哈希值,也就是每个commit的引用。

# objects文件夹

这个目录里面就存放了git仓库的所有文件,其中有的琐碎文件会被打包成pack,就会被放到pack文件夹下。其他文件夹里的文件类型主要有commit,tree,blob。注意在git中只要文件的内容一样,那么它就是唯一的一个blob。

# git中的文件类型

git本地仓库的文件夹里面文件类型主要有commit,tree,blob。那它们之间的关系是怎样的呢?

  • 一个commit只对应一个tree,这个tree对应当时文件夹的更改信息,也就是提交时的文件快照信息。
  • 当前工作目录下的文件夹在git本地仓库中是一个tree。
  • 当前工作目录下的文件,不管文件名,类型是什么,只要文件的内容相同,在git本地仓库中只是一个blob文件。
  • tree和blob的关系更像是文件目录和文件的关系。

# 深入理解头指针

# 分离头指针

git checkout + 分支名,一般是切换到相应分支上,但是如果后面不是分支名,而是commit的Hash值,即便该commit在该分支上是最新的commit,那它依旧处于分离头指针的状态,称为detached HEAD。

分离头指针有不好的地方就是不会纳入git的分支管理中,找不到处于分离头指针状态下提交的commit,但我们也可以利用这点添加尝试特性,如果满意再为这些commit添加分支,如果不满意就直接切回其他分支,这些commit到后面会被清除。

# 深入理解HEAD和Commit

Git的HEAD指针一般指向的是当前的分支最新commit,切换分支时会自动指向被切换的分支的最新commit;但是当它指向不是最新的commit时,基于此commit的后面提交的commit是不与任何分支挂钩,处于detached HEAD状态时,切换到新分支时会丢失后面提交的commit,此时git会提示是否将后面提交的commit设置为一个新的分支。总之HEAD指针不管指向哪个,它都是指向commit的。

# HEAD的用法

比如我们比较两次commit的比较:

git diff 4b1534018 e4abe2ef

如果使用head的话,我们可以:

git diff head^ head
git diff head^^ head
git diff head^1 head
git diff head^1^1 head
git diff head~ head
git diff head~~ head
git diff head~2 head

# git中重命名文件

在git仓库中有一个hello.txt的文件,我们想重命名为hi.txt:

> mv hello.txt hi.txt
> git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    hello.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        hi.txt

这个时候运行

git add .

或者是:

git rm hello.txt
git add hi.txt

git会自动识别出我们是更改文件名,此时使用git status会出现:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    hello.txt -> hi.txt

注意,如果只运行git add hi.txt,它是只添加文件,并不能识别出是重命名该文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

从上面看出有点繁琐,而且语义不明确容易出错,我们使用一条命令来重命名文件:

git mv hello.txt hi.txt
1

# 查看版本库历史

# 以命令行方式查看版本历史

查看当前分支的历史

git log

查看所有分支的历史

git log --all

以图形化的方式查看,能清楚看到分支演变

git log --graph

以简洁的方式查看:

git log --oneline

查看最近2次提交历史:

git log -n2

查看某个某个版本的文件内容:

git show commitHashCode:./filename

# 以图形化界面查看版本历史

在目录下面输入

gitk

如果想看到更多信息,可以点击view->new view来查看更多的信息

# git查看某个命令帮助

查看某个命令的帮助:

git help --web log

# git分支命令

查看分支,并以简洁形式显示最近的commit:

git branch -v

查看远程和本地的所有分支:

git branch -a

创建分支:

git branch branchName

切换分支:

git checkout branchName

如果需要在创建分支的同时同时切换到分支,可以用一条命令:

git checkout -b branchName

如果创建分支时指定从某个commit开始创建:

git checkout -b branchName commitHASH
git checkout -b newbranchName fromBranchName

删除分支:

git branch -d branchName

# git内部查看命令

根据哈希值来查找该文件的类型

git cat-file -t 752fd47

根据哈希值来查看文件的内容

git cat-file -p 752fd47

# git常见问题

# windows乱码

在windows下使用git,在使用git status或者git diff时候,遇到中文会显示乱码,为了解决这个问题,可以在windows下添加系统环境变量LESSCHARSET为utf-8即可。

# Gitea

在Gitea下如果需要向其他服务器拷贝仓库,需要设置如下:

[migrations]
ALLOWED_DOMAINS = mygitea.com
1
2

但还是建议直接拷贝Gitea服务器的仓库文件。

# 清除untracked文件

有时候需要清除untracked文件,这个时候需要:

确认要删除的文件夹和文件

git clean -fd -n

删除文件和文件夹:

git clean -fd

-f为清除文件,-d为清除目录。

# GitPull终止

如果Git Pull的过程中终止,就会出现拉取不完整。需要清除这些不完整的部分:

git reset --hard
git clean -fd -n
git clean -fn

# GitLFS丢失

显示Error downloading object: Content/test.umap: Smudge error: Error downloading Content/test.umap: batch response: Not Found

网上的解决方案:

git lfs pull --all

实际上这种情况的出现是由于git能访问,但是git lfs地址和原来的不一样,最常见的是内网有一个git服务器,我们访问是http://Ip:port,若要把它映射到外网,我们访问则是http://Ip。这个时候我们的git lfs是无法访问的。

# GitLFS文件不一致

如果发现LFS文件和库里的不一致,需要使用:

git lfs checkout .

# Git路径问题

如果显示问题:

error: invalid path 'drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/'
1
2
3
4
5

这个时候需要:

git config --global core.longpaths true
git config --global core.protectNTFS false
git config --global core.filemode false
1
2
3