「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」
前言
作为一个开发者,想必大家都清楚Git。无论大家在工作中是使用命令行还是可视化工具来操作Git,应该都已经熟悉和掌握其基本的使用。而本系列文章的目的在于向大家介绍Git背后的基本原理,例如其如何实现不同版本的代码保存及版本切换。此外,还会向大家介绍一些好用的命令,解决在平常使用中的困惑。
.git目录结构
我们先学习下Git的目录结构
通过命令行来初始化个仓库
mkdir mygit
cd mygit
git init
我们使用tree命令来打印此时的.git目录结构
.
├── HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.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
HEAD
HEAD文件保存着一个指针,实际指向当前工作环境对应的分支
ref: refs/heads/master
当我们切换分支的时候,实际上HEAD的内容也会跟着变化
config
config对应我们仓库的一些配置
[core]
repositoryformatversion = 0
# 视文件权限的修改是否为差异
filemode = true
# 裸仓库 在使用 git init --bare 时会创建裸仓库也就是没有.git文件夹而是将所有文件暴露在主目录
bare = false
logallrefupdates = true
# 忽略文件名的大小写
ignorecase = true
precomposeunicode = true
在初始化时config只有core部分的配置,实际在后面我们会加入一些用户配置
git config user.name xxx
git config user.email emain
[user]
name = xxx
email = email
以及仓库分支的配置
# 远程仓库配置
remote "origin"]
url = ssh://git@gitlab.xxx.cn:8888/xxx.git
fetch = +refs/heads/*:refs/remotes/origin/*
# 分支配置
[branch "master"]
remote = origin
merge = refs/heads/master
hooks
hooks下面保存一些钩子文件,用于保存在特定节点执行的bash命令。初始化的时候文件里面保存的都是示例,如果需要启用可以把文件名后缀.sample删除
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:
info
里面包含exclude文件,作用和.gitignore相似,区别在于exclude文件修改不会保存在版本中而.gitignore则会。
objects
objects是git版本控制的核心,我们可以将其理解为版本仓库。下面包含两个文件夹info和pack,初始时没有内容。我们后面会重点分析这一部分。
其它
我们刚才只是分析了git初始化的目录结构,实际在使用过程中还会生成很多其它的目录及文件。我们来看个使用中的项目下的.git目录结构。
tree -I objects
我们忽略objects目录,在后面的分析中再详细分析
├── COMMIT_EDITMSG
├── FETCH_HEAD
├── HEAD
├── ORIG_HEAD
├── config
├── description
├── hooks
│ ├── applypatch-msg
│ ├── applypatch-msg.sample
│ ├── commit-msg
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ ├── heads
│ │ ├── master
│ │ └── debug
│ └── remotes
│ └── origin
│ ├── HEAD
│ ├── master
│ └── debug
├── packed-refs
└── refs
├── heads
│ ├── master
│ └── debug
├── remotes
│ └── origin
│ ├── HEAD
│ ├── master
│ └── debug
└── tags
├── release_202003181722
└── release_202002130082
COMMIT_EDITMSG
临时文件,保存最后一次本地提交的commit信息。例如当我们pull远程代码更新本地仓库时,可以通过COMMIT_EDITMSG来找到我们上一次提交的位置。
修复xxx问题
FETCH_HEAD
在我们执行fetch命令的时候会更新此文件,文件保存了我们拉取的远程分支对应的信息版本号(远程) 分支仓库地址。还有个关键字not-for-merge,其表明不需要合并的分支,什么意思呢?
例如我们当前在master分支,那么在当前分支执行pull命令的时候只会讲远程的master进行合并,就是因为有not-for-merge标记其它分支。所以当我们切换分支再fetch的时候,分支对应的not-for-merge也会更新。
f2357a770980f131fdcc5746adc4f7d2566dc86 branch 'master' of ssh://git@gitlab.xxx.cn:8888/xxx.git
632371e033b3352828dac377b1ab63ca07c3c44 not-for-merge branch 'debug' of ssh://git@gitlab.xxx.cn:8888/xxx.git
ORIG_HEAD
2322e639d28e52544dd7565fb341eff82313de29a
ORIG_HEAD用于备份HEAD对应的版本号,在我们执行以下操作时会进行备份
git reset
git merge
git rebase
git pull
这也就可以理解为什么我们在解决冲突的时候,可以通过abort来放弃并退回之前版本
index
index是个二进制文件,属于git的核心实现。其对应我们所说的暂存区,所以通过其可以得到我们整个暂存区的仓库文件,当然其主要保存的是各个文件对应的指针,在此不多分析。
logs
logs中保存对应分支的操作日志,也就是我们使用reflog可以查看到的日志。
refs
refs目录用于保存当前各种分支或者tag下对应的版本号(commitId)。
分为三个部分
| 目录 | 内容 |
|---|---|
| heads | 本地各个分支对应的commitId |
| remotes | 远程各个分支对应的commitId |
| tags | 各个tag对应的commitId |
从此处可以看出,分支和tag实际是指向某个commitId的指针。