理解Git原理
type
Post
status
Published
date
Nov 23, 2025
slug
how-git-works
summary
git实现原理
tags
开发
category
技术分享
titleIcon
password
icon
insider
参考资料
- 截止2025.11.27的41节内容

git核心工作原理
先导知识
hash算法
- git中用作存放于objects目录下对象的标记(指针)
- git默认sha1,带碰撞检测的版本,可以手动指定sha256,在浏览器(浏览器中使用SHA1存在伪造签名的风险(哈希碰撞)),Docker等其他地方一般更多使用sha256


- 两个不同的文件在sha1下哈希冲突示例

- 换为sha256,不再冲突

- 验证git的sha1不会冲突

辅助命令
- git cat-file
- -t 哈希值(前几位即可) 查看objects中文件的类型
- 常见4种,按命令引入分开写
- git add → 引入blob,在不执行压缩命令情况下一个blob全量存放一个文件的一个版本的内容
- git commit → 引入tree与commit,commit指向commit命令时的tree,tree指向commit时目录下所有文件最新版本的blob以及作为子目录的tree
- git tag -a ‘’ → 引入tag,tag静态指向关联的commit,区分于branch的动态(永远指向在对应分支下最新的commit)
- 附图参考各自命令对应版块
- -p 查看objects下二进制文件内容
- -s 查看文件长度


- git ls-files -s
- 权限 SHA1哈希值 文件名
git add
blob类型 长度 内容
在index存放文件名
(git add - blob | git commit - tree,commit) git objects文件与关系
git add
- 关键行为:index文件中存入文件名等元信息,objects下创建blob文件
- objects文件夹下,哈希值前两位命名的文件夹+后续38位命名的文件(blob类型,存储内容为压缩头+压缩后内容,采用zlib压缩,哈希值源于对“blog 数据长度\0数据“SHA1运算,不存冗余,一样的内容objects不会有额外文件)


- 验证SHA1生成源 - blob {size}\0{content} (注:echo生成的字符串末尾带\n)

- 验证zlib压缩的解压过程


关系总览
- git objects下各文件(blob,tree,commit,均用hash索引到)与关系
- HEAD → 当前分支 → 分支对应最新commit(同时带parent commit) → tree(文件目录) → 文件/子tree(子目录)


文件内容
- blob存放文件的不同版本内容

- tree存放
- 权限(040000-文件夹,100644-一般文件,owner读写,others读) 类型 对应版本的hash 文件名 (通过hash找对应版本)

- commit存放
- 对应tree
- parent commit
- 作者信息 时间戳 时区
- 提交信息

git checkout 切换分支
- 通过checkout命令移动HEAD指向,可切换到某个分支(间接指向某个commit)或具体commit(直接指向,此时为detached HEAD)



- 在detached HEAD下保留commit,git checkout -b 新分支(在新分支上提交),提示的方法是使用git switch -c 分支名(如果已经提交了)
git branch -d/-D 删除分支
- 删除分支只删除了分支本身(分支本身内容为commit的hash),也就是仅删除了指向某一commit的指针,不影响objects目录下的commit、tree、blob,如果在未合并下误删某分支,可以进入detached HEAD状态(checkout到对应commit的哈希值,要查找删除的分支对应commit的hash参见下方git reflog),然后重新创建分支(方法参见上方)

git reflog 查找过往提交的分支hash
- 比如此处已经删除的dev分支上的最近一次提交,ab2fb9f


- 切换后对应分支上的新文件(对应commit下add引入的新文件)恢复

- 重新checkout -b 分支恢复


git status 文件状态

git branch
- git branch查看所有分支(无参 - 本地,-a 本地 + 远程)
- git branch 分支名 创建分支(不切换)

- 从当前分支创建出的分支与当前branch指向同一个commit

- checkout改变HEAD指针,指向新的分支

- -d删除分支(报错未合并分支),-D强制删除



- git branch -vv,详细信息,含与远程的tracking关系
git diff
git gc
- 从对每个版本的全量存储变为
- .idx
- .pack
- git add后创建的objects(commit,blob,tree)默认全量存储(整个文件内容),每个版本都是以完整文件存储。

gc压缩前pack info 0B

pack info

参数给.idx .pack都可
git verify-pack -v ./git/objects/pack/pack-hash.idx
tree和blob都有基线版本,commit没有

后续blob存储增量(delta),最后跟初始版本hash
git clone时已经压缩(git push)



12objects → 4次commit 单次3个对象(blob | tree | commit)
delta 3 → 4次commit 3次增量更新

idx索引,压缩后仍可便捷地按原本的哈希值查看文件内容(压缩前)






index二进制文件,其中包含文件名


















- git clone网络传输会进行压缩,git clone下来objects下pack与info两文件夹不为空
中间add状态无效,产生垃圾blob(无tree引用)
只有最后一次add有效



git prune -n → dry run,提示作用对象

git fsck 找出未引用blob
git prune清除未引用对象

除了中间状态的add,其他产生垃圾的情况
新分支下提交新文件,删除此分支,确定不恢复分支,对应commit,tree,独有的blob均为垃圾


git prune不会删除

git gc复用旧有,同时压缩新的3个(垃圾对象)

从git fsck和git prune行为看出,git不认为是垃圾对象


清除错误添加的内容(大文件,隐私文件)
git merge
fast forward

仅做指针移动
仅master分出的bugfix做修改
master不变

17 files → 18 files

ORIG_HEAD head的之前状态


3 way merge




ORIG_HEAD master分支前一次

应是类似菱形的效果,但git log线性展示
可使用git log —graph或其他可视化工具










git commit产生merge commit,有两个parent commit

fast forward线性历史合并,隐藏被合并分支的信息,不易看出(给主分支的变化(添加的commit)?合并与修改时间?)

3 way merge仍然保留

已经分叉的情况下做fast forward(分叉改线性,不产生一个有两个parent的merge commit)→ rebase
fast forward的条件是,待合并的分支修改是基于当前分支的最新commit做的
如果不是基于最新,要使用fast forward就rebase这些修改到最新的commit上
被合并分支上的commit哈希值会重新生成


不创建merge commit保持线性历史
会对历史有影响 → 对移动的commits会生成新的hash(如果之前相关历史已经push,会在rebase后push出错,需要强制push,历史结构的改变会影响其他人工作,不适宜在仅自身使用的分支以外的分支,以及主分支上进行)
git tag
- branch是指向commit的指针,内容为commit的hash,但branch是动态的,永远指向最新的commit,如果需要静态的指针,仅与某一commit一直关联在一起,需要tag,git tag为轻量级tag,即只在refs/tags下创建对应tag,不在objects下产生文件,进而不能存储元信息(日期,tag信息,创建人)



- objects下的tag类型文件

- 只删除tags下文件,不删除objects下内容

- 时间不一样,hash不一样(name与message相同也不会撞)


git clone
- 远程refs信息压缩到packed-refs里



git branch -r查看远程分支

packed-refs下远程master不保证最新 → git fetch取到最新(检查远程分支情况并同步每个分支最新)
新分支与分支新commit

检查远程分支与本地关联情况

git fetch

压缩的packed-refs可能滞后,git fetch后refs remotes的会更新

- 远程超前,git log看不到了

git branch -a查看本地与远程分支

git branch -r 未看到新分支






new tracked
git branch -r


不同步本地不修改,git remote show origin
git fetch对每个对应远程分支拉取新的commit,但对于远程删除不做处理(对本地没有,远程有的拉取,但对本地有远程没有的不做处理)

stale状态,git remote prune

git fetch —prune


git branch -vv
没有与远程关联的分支



git pull | git fetch & git merge | git clone | git push 本地与远程仓库交互
添加远程源
- git remote add 远程源名 url

- 对应会在.git/config的本地配置下添加对应内容

- git push时会进行对象压缩,便于网络传输

git fetch与git pull
- git fetch,按fetch的字面义,不是同步,是用于获取每个远程分支(如origin/master → 远程源/远程分支)在远程有而本地没有的内容
- git pull → git fetch & git merge二合一



- fast forward merge情形(直接完成)

- 3 way merge情形(处理冲突,弹出文件要求编辑commit信息)

git clone

无冲突下自动merge commit,git pull合并3 4 步



一个是本地直接的提交(平级),一个是merge commit





FETCH_HEAD

FETCH_HEAD记录远程分支最新commit









回滚merge

远程关联创建
远程删除
git push
- git push → 对默认源的当前分支(需要预先关联)提交
- git push -u 源 分支,提交对应分支到指定源,并关联本地与远程分支(同名)
- git push 源 -d 分支,删除远程对应分支,不需要关联上



- 如不使用-u(对本地有远程没有的分支首次push而言),本地与远程的分支不会关联上,后续的git pull与git push不能省略参数,均需要手动指明源与分支
- 直接git push,不指明源与分支,被拒绝

- 新分支初次push使用-u 关联,之后可以省略参数


hooks
- 生命周期图

- 默认实现语言为shell,可换
- 去掉.sample后即生效



- 如果exit 0仍会执行操作,如果exit 1则不执行操作退出


- hooks不会推送到远程

git cherry-pick





历史不同hash不同,但内容一致


git patch | apply
git format-patch
最后几次提交
邮件头
git diff内容
git version内容

git apply .patch文件路径


apply后相当于改了文件,但仍需要add与commit

git send-email .patch文件路径










改本地的
















pull fetch push
git stash & git worktree
git cherry-pick
hooks pre-commit
git config
配置读取优先级
- 优先级上local(当前仓库配置.git/config) → global(全局配置 ~/.gitconfig) → system(win下安装目录/etc/gitconfig)

- system一般不做修改,主要修改全局global与本地,适宜global的常见配置项:
- 代理http.proxy https.proxy
- 命令别名alias
- 信任文件夹safe.directory
修改设置
- 直接修改对应级别的配置文件
- 使用命令:
- 写入:git config (—global) 配置项(如http.proxy) 内容(如http://127.0.0.1:7898)
- 删除:git config (—global) —unset 配置项
- 查看:git config (—global,筛选级别,不写展示global + system) -l(或—list)(—show-origin对应配置的来源文件)

多用户身份
- 区分工作与个人账户
alias 命令别名
git worktree 分支并行 | git stash 暂存修改
git stash
- 应用场景:当前分支有未完成的工作,不打算提交,需要暂停工作,先切其他分支完成对应工作,再切回来继续
- 问题:如果直接切会将文件状态带到切换的分支


- git stash暂存后切换分支

- 完成工作切换回来,git stash pop复原暂存内容

git worktree
- 把某个分支创出一个单独的文件夹

- 工作完成后回到原目录

其他少见的
git unpack-objects 解压压缩后的对象
- git unpack-objects < .pack文件(已有的不会unpack,需移动pack文件位置)


git submodule
- 共享依赖
- 结合worktree分支并行


git format-patch & git apply & git send-email
git diff






左索引区 右工作区(有哈希值,没有文件)
- 显示从 -(索引区)的第四行开始共7行的内容


- 对比索引区与代码仓库不同








