「这是我参与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的指针。