Git 本地仓库的一些概念

Git 是一个分布式的文件管理和版本控制工具,本文主要从 Git 的快照记录方式和分布式工作方式上介绍一些基础。私以为对 Git 的这些工作方式的认知,会对理解和优化 Git 的日常使用的一些命令比较有帮助。

推荐阅读

GitBook : https://git-scm.com/book/zh/v2

廖雪峰:https://www.liaoxuefeng.com/

一、基础概念

分布式、文件管理、版本控制

什么是分布式?如下图:

集中式v.s.分布式版本控制

如图,每一个 version 其实就是一个版本的数据存储。SVN 等集中式的版本控制工具,是将所有的版本信息存储在远程版本仓库中,如果断网,则无法工作。而Git则不同,Git的每一台 clone 过某个仓库的电脑或者服务器,都是一个完整的仓库,他的版本信息都存储在本地。换句话说,Git 的 commit 操作其实是提交信息到本地仓库,所以 Git 在断网的情况下也可以进行版本管理工作,只有在从远程仓库获取更新以及推送信息到远程仓库时,才需要联网。

二、本地仓库的文件存储和版本管理

1.本地仓库的文件存储

在工作目录下,进入 .git 目录,查看 tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.
├── HEAD
├── branches
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
├── heads
└── tags

本地工作目录下面的 .git 目录就是本地的版本仓库,其中各个目录和文件的内容如下:

  • hooks : 这个目录存放一些shell脚本,可以设置特定的git命令后触发相应的脚本;在搭建 gitweb 系统或其他 git 托管系统会经常用到 hook script
  • info : 包含仓库的一些信息
  • logs : 保存所有更新的引用记录
  • objects : 所有的Git对象都会存放在这个目录中,对象的SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名
  • refs : 这个目录一般包括三个子文件夹,headsremotestagsheads 中的文件标识了项目中的各个分支指向的当前 commit
  • COMMIT_EDITMSG : 保存最新的 commit message,Git 系统不会用到这个文件,只是给用户一个参考
  • config : 这个是GIt仓库的配置文件
  • description : 仓库的描述信息,主要给gitweb等git托管系统使用
  • index : 暂存区(stage),是一个二进制文件
  • HEAD : 这个文件包含了一个档期分支(branch)的引用,通过这个文件Git可以得到下一次commit的parent
  • ORIG_HEAD : HEAD 指针的前一个状态

本地仓库文件的四中状态:

本地仓库文件的四种状态

对应的各种git操作:

1
2
3
4
{untracked/modified => staged}
git add
{staged => unmodified}
git commit

注意:

(1)git checkout : 优先恢复到staged

(2)git diff : modified vs staged | unmodified

git diff —cached : staged vs unmodified

(3)途中的 remove 文件指的是执行 rm 操作物理删除文件,如果是执行的 git rm 操作,被删除的文件会直接变成 staged 状态

2.所谓版本:commit 的时候发生了什么

git commit

结合前面提到的本地版本库,其实本地版本库就是一堆指针和一堆文件快照。

如图,每次 commit 的时候,有变化的文件会生成一个文件快照,然后本次 commit 生成一个 commit 指针,commit 指针指向一个 tree 文件,tree文件记录的是本次 commit 时候的所有文件的快照。

而每个分支的指针其实就是指向当前分支最新一次commit。而本地仓库的 head 指针,就是指向当前分支。

由此,git checkout 切换分支,实际就是改变当前 HEAD 指针的指向,git reset 操作实际就是将当前分支的指针指向某次 commit 记录。

另外:

  • git add 操作(文件转为 staged 状态)也会生成 blob 快照文件,且 git add 生成的 blob 文件容易变成“悬空对象”
  • commit 记录的是完整的文件状态,所以 git reset 操作其实可以恢复到任何一次 commit 的完整状态,也能找回所有存在过git中的文件,无论你是否进行过分支的merge操作

三、分享一些命令,值得逐条研究

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 全局配置开发者信息,建议开发目录中在 .git/config 中配置当前仓库开发者信息覆盖全局信息
git config --global user.name "username" #配置本地git账户的姓名
git config --global user.email "username@email.com" #配置本地git账户的邮件

git remote [-v] #显示所有的远程仓库
git remote add <name> <url> #添加远程仓库
git remote rm <name> #删除相应的远程仓库
git branch [-v] #显示所有的本地分支
git branch [-r] #显示所有的远程分支
git branch <branchname> #创建相应的本地分支
git branch [-d|D] <branchname> #删除相应的本地分支
git checkout <branchname> #切换到相应分支
git chechout [-b] <branchname> <repository>/<branchname> #创建对应的本地分支,并获取对应远程分支的代码
git add [filename | .] #添加相应文件到暂存区
git commit [-m] <comment> #将暂存区的更改添加到本地仓库
git log #列出当前分支的提交记录
git push <repository> <local branchname>:<branchname> #将本地分支的内容推送到对应的远程分支,如果远程分支不存在则创建对应的远程分支
git push <repository> :<branchname> # 删除远程仓库分支
git merge <branchname> #合并对应分支到当前分支
git rebase <branchname> #将对应分支的变更衍生到当前分支

git commit --amend 重新编辑commit信息
git branch -r -a -l 查看所有远程分支

git reset --hard {commit-hash} 回退到某次提交,并撤销之后的所有修改
git reset --soft {commit-hash} 回退到某次提交,并保留之后的修改(modified)

git revert {commit-hash} 撤销某次操作

git rebase --continue rebase 冲突解决后,先git add 后执行此命令

git log --oneline commit-short-hash & commit-message 一行显示
git log --pretty=oneline commit-hash & commit-message 一行显示


# 一条神奇的查看git log 的命令,建议尝试
git log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

# 恢复到上次commit的状态,完整恢复,包括gitignore的文件
git reset --hard;git clean -df

# 从某次commit中提取文件
git checkout <commit-hash> --filename
# 将某次commit(可以是其他分支的)修改的所有内容提取到当前分支
git cherry-pick

# 查看fileName文件第160最近10次的修改记录
git blame -L 160,+10 <filename>

git reflog # 查看提交版本历史记录
git fsck # 运行一些仓库的一致性检查, 如果有任何问题就会报告. 这项操作也有点耗时, 通常报的警告就是“悬空对象"(dangling objects).git add 产生的