Git原理
Git 是一个分布式版本控制系统,它用于跟踪文件的更改历史并支持多人协作开发。Git 的基本原理是通过记录文件的 快照 和 变更 来管理文件版本,使得团队能够高效地进行并行开发和历史追踪。下面是 Git 工作原理的核心概念和机制。
Git 数据模型
Git 的数据模型基于对象存储,文件和历史记录都以对象的形式存储。Git 主要管理四种类型的对象:
Blob(文件对象)
- Blob 是 Git 用来存储文件内容的基本单元。每个文件在 Git 中都有一个对应的 blob 对象,保存文件的内容。
- Git 不存储文件的名称和目录结构,而是通过树对象(tree object)来组织这些内容。
- 当文件的内容发生变化时,Git 会为其生成新的 Blob 对象,并基于该内容生成一个唯一的哈希值。
Tree(树对象)
- Tree 对象用来表示目录结构,它包含对文件(Blob 对象)的引用,以及指向其他子目录的指针(即对其他 Tree 对象的引用)。
- 每个 Tree 对象都有一个目录结构,并包含每个文件及子目录的路径和相应的哈希值。
Commit(提交对象)
Commit
对象记录一个版本的快照,它包含:
- 指向树对象的指针,树对象描述了该提交时所有文件的状态(包括文件和目录)。
- 提交的元数据,如作者、日期、提交信息等。
- 对于每次提交,Git 会将该提交与前一个提交的哈希值(父提交)进行连接,从而形成提交历史链条。
tag(标签对象)
- Tag 是 Git 中的一个引用,通常用于标记一个特定的提交,如版本号。标签对象并不存储文件数据,而是指向一个提交对象。
Git 工作流程
Git 的工作流程可以分为以下几个区域和步骤:
工作目录(Working Directory)
- 工作目录 是你本地的项目文件夹。它包含你正在编辑的文件,以及当前项目的最新版本(但并不是 Git 管理的所有版本,只有当前的工作副本)。
- 在工作目录中,文件是可见的、可修改的,用户可以进行编辑操作。
暂存区(Staging Area)
- 暂存区 也称为 索引(Index),是 Git 用来临时保存文件改动的地方。当你执行
git add
时,Git 会把工作目录中的文件变动添加到暂存区,准备进行提交。 - 暂存区中的文件是快照级别的内容,Git 会记录这些文件的状态,以便在执行
git commit
时将它们提交到版本库中。
本地仓库(Repository)
- 本地仓库 存储了你的 Git 项目的历史记录和所有对象(如提交、树、文件内容等)。
- 本地仓库的实际数据存储在
.git
目录中,所有的提交记录、对象、配置文件等都在这个目录下。
远程仓库(Remote Repository)
- 远程仓库 是托管在服务器上的 Git 仓库,供团队成员共享和协作开发。
- Git 通过网络连接到远程仓库,你可以将本地的更改推送到远程仓库,也可以从远程仓库拉取更新。
- 常见的远程仓库有 GitHub、GitLab、Bitbucket 等。
Git命令
Git配置
初始化仓库
1 | git init |
其余参数
--initial-branch
初始化的分支--bare
创建一个裸仓库(纯Git目录,没有工作目录)--template
通过模板来创建预先构建好的自定义git目录
查看git文件目录
1 | tree .git |
输出:
1 | .git |
config
:Git 配置文件。description
:一个可选的仓库描述文件。HEAD
:指向当前分支的引用。objects
:存储 Git 对象(如提交、树、文件等)。refs
:存储引用(例如分支和标签)。
配置git
1 | git config [<options>] <name> <value> |
系统级配置:对所有用户和仓库有效,配置存储在系统级别的配置文件中,通常是 /etc/gitconfig
文件。(--system
)
全局配置:对当前用户的所有仓库有效,配置存储在用户目录下的 .gitconfig
文件中,通常是 ~/.gitconfig
(Linux/macOS)或 C:\Users\<UserName>\.gitconfig
(Windows)。(--global
)
仓库级配置:只对当前仓库有效,配置存储在当前仓库的 .git/config
文件中。(--local
)
配置远程仓库
1 | git remote |
用于管理远程仓库
查看远程仓库
1 | git remote -v |
添加远程仓库
1 | git remote add 仓库别名 远程仓库的地址(可Http或ssh) |
同一个origin
设置不同的push
和fetch
1 | git remote set-url --add --push(or fetch) origin 远程仓库地址 |
提交代码
添加文件至暂存区
1 | git add file_names |
此时objects
下会多出一个目录,但没有生成提交。
当执行 git add
操作时,Git 会对文件的内容计算哈希值,并生成新的 blob 对象。这些对象会被存储在 .git/objects/
目录中,并且根据哈希值的前两位字母和后面的内容划分到子目录中。
提交文件
1 | git commit -m "提交信息" |
创建一个新的提交对象(commit object
):
- Git 会根据 暂存区 的内容(包括文件和文件的元数据,如修改时间、文件路径等),生成一个新的 提交对象。
- 提交对象包含:
- 提交的作者信息(如姓名、邮箱)。
- 提交的时间戳。
- 父提交的哈希(前一个提交的哈希值,如果是第一次提交则没有父提交)。
- 一个指向 树对象(
tree object
)的哈希值,树对象描述了该提交所涉及的文件和目录结构。 - 提交信息(即
git commit
时输入的 commit message)。
这个 提交对象 会根据 Git 的哈希算法(SHA-1)生成一个唯一的 ID,通常是一个 40 字符的十六进制字符串。
- Git 对象存储
- 该提交对象会被存储在
.git/objects/
目录下,路径为/.git/objects/{commit-hash}
。 .git/objects/{commit-hash}
文件存储着完整的提交对象数据。
- 该提交对象会被存储在
创建或更新树对象(tree object
):
- 提交对象还会包含一个指向 树对象(
tree object
)的哈希。树对象保存了该提交的目录和文件信息,并且会记录每个文件的哈希值(文件的内容哈希值)。 - 如果你在
git commit
之前修改了文件并通过git add
将其添加到暂存区,那么这些文件会在树对象中保存为 blob 对象的引用。 - 树对象也会存储在
.git/objects/
中,路径为/.git/objects/{tree-hash}
。
更新 HEAD
文件:
HEAD
文件指向当前正在工作的分支。在执行git commit
后,Git 会将当前分支的引用更新为新的提交哈希。- 例如,如果你在
master
分支上进行提交,Git 会更新.git/refs/heads/master
文件,记录该分支指向的新提交哈希。 .git/HEAD
文件本身通常指向refs/heads/
下的当前分支。执行git commit
后,HEAD
会指向新提交的哈希值。
**更新 .git/logs/HEAD
和 .git/logs/refs/heads/
**:
- Git 会记录提交历史,并将每次提交的引用信息保存在
.git/logs/
目录下。这使得 Git 可以回溯和恢复历史记录。 - 文件如
.git/logs/HEAD
和.git/logs/refs/heads/{branch-name}
会被更新,记录该分支的最新状态。 .git/logs/HEAD
中会保存 HEAD 变动的记录,而.git/logs/refs/heads/
中会保存该分支的所有提交历史记录。
更新 .git/index
(暂存区):
git commit
后,暂存区中的文件会被清空。也就是说,暂存区会恢复到未跟踪的状态,不再保存已提交的更改。
注:寻找Objects中的信息
通过
commit
寻找tree
信息,每个commit
会存储对应的tree id
1
git cat-file -p commit-id
通过tree存储的信息,获取到对应的目录树信息
1
git cat-file -p tree-id
从tree中活动吗blob的id,通过blob id获取对应的文件内容
1
git cat-file -p blob-id
Refs文件存储的内容
refs的内容就是对应的commit id,因此把ref当作指针,指向对应的commit来表示当前ref对应的版本
分支与标签
创建分支
1 | git checkout -b |
分支一般用于开发阶段,可以通过添加commit进行迭代的
创建并切换到分支
1
git checkout -b 分支名
创建分支
1
git branch 分支名
切换到分支
1
git checkout 分支名
查看当前分支
1
git branch
合并分支
先切换分支
1
git checkout 分支
注:更为合适的切换分支的命令 switch
创建并切换到新分支
1
git switch -c 分支名
切换到当前有的分支
1
git switch 已有分支名
合并分支
1
git merge 需要合并的分支
合并分支并禁用
Fast forward
,一般默认Fast forward
模式1
git merge --no-ff 需要合并的分支
合并分支同时增加提交信息
1
git merge -m "提交信息"
删除原有分支
1
git branch -d 分支名
强制删除分支
1
git branch -D 分支名
解决冲突
当git无法自动合并分支时,使用
git status
可查看冲突的文件,然后手动解决冲突后再提交,可使用如下命令查看分支合并图1
git log --graph
分支管理策略
master
用于发布稳定版本,dev
用于平时修改,团队合作的代码都上传至dev
中Bug
分支面前有两个分支,一个修改了一半,但没有完成,不能提交,一个是上个版本急需修改的
bug
,现在需要将修改一半的当前工作现场隐藏起来,等以后恢复现场后继续工作。1
git stash
查看原有存储位置
1
git stash list
上一版本的
bug
修复之后,恢复原有分支进度1
git stash pop
将修改后的版本复制到
dev
版本上1
git cherry-pick bug修复后版本的commit-id
推送分支
1
git push origin 远程分支名
克隆远程分支
首先创造远程
origin
的分支dev
1
git checkout -b dev origin/dev
此时可以正常
push
修改内容到dev
版本别人的推送和本地推送产生冲突
先抓取远程分支,然后合并,然后再推送
1
git pull
若提示没有和远程库链接,先使用如下命令
1
git branch --set-upstream-to=origin/dev dev
再尝试
pull
,然后先合并,若有冲突先解决冲突,之后再提交Rebase
把本地未push的分叉提交历史整理成直线
1
git rebase
创建标签
1 | git tag |
标签一般表示一个稳定版本,指向的commit一般不会变更。
打标签
1
git tag 标签名
默认标签是打在最新一次提交中
查看已有标签
1
git tag
给历史提交打标签
1
git tag 标签名 历史提交commit-id
查看标签信息
1
git show 需要查看的标签名
创建自带说明的标签
1
git tag -a 指定标签名 -m 提交信息 commit-id
删除标签
1
git tag -d 需要删除的标签名
推送标签
推送某一个
1
git push origin 标签名
一次性推送全部
1
git push origin --tags
删除远程标签
先删除本地标签
1
git tag -d 标签名
再删除远程标签
1
git push origin :refs/tags/标签名
修改历史版本
修改最近一次的commit信息
1 | git commit -amend |
修改最近三个的commit信息
1 | git rebase |
删除所有提交中的某个文件
1 | git filter -branch |
提交到远程仓库
克隆
1 | git clone |
拉取完整的仓库到本地
拉取
1 | git fetch |
将远端的某些分支最新代码拉取到本地,不会执行merge操作
会修改refs/remote的分支信息,如果需要和本地代码合并需要手动操作
拉取和合并
1 | git pull |
相当于git fetch
加git merge
Git研发过程——分支管理流-Github Flow
只有一个主干分支,基于Pull Request往主干分支提交代码
工作流程
- 创建一个
main
主分支 - 创建一个
feature
分支 - 创建一个
feture
到main
的Pull Request
Git与GitHub(实践篇)
先Fork
,在自己仓库产生分支,然后pull request
推送到原有开源仓库
Git初始化
配置姓名和邮箱地址
1
2git config --global user.name "xyz"
git config --global user.email "xyz@gmail.com"创建验证使用的公钥
1
ssh-keygen -C "xyz@gmail.com" -t rsa
在Github中加入公钥
本地创建好仓库,推送到远程仓库
查看远程仓库地址
1
git remote (-v 同时列出远程仓库地址)
1
git remote set-url 远程仓库别名 地址 //修改远程仓库名和地址
添加远程仓库
1
git remote add 远程仓库的别名 远程仓库的地址(http和ssh都可,推荐SSH)
更改本地仓库名(新建的远程仓库名和本地名字一般不一样,需要更改为一致)
1
git branch -m 新名字 (-m 变 -M 强制修改)
先拉取远程仓库(两端先同步)
1
git pull --rebase 远程仓库别名 本地仓库分支名 //当两端的commit不一致时,需要使用rebase调整commit
本地提交到远程
1
git push 远程仓库名 本地仓库名
从GitHub远程仓库更新本地代码
基本思路:将远程代码下载到本地新建分支,对比区别后再合并。
1 | git fetch origin master:temp |
代码解释:从远程仓库分支master下载代码到本地新建分支temp
1 | git diff temp |
代码解释:查看temp分支代码与主分支的改变情况
1 | git merge temp |
代码解释:合并分支
1 | git branch -d temp |
代码解释:删除本地分支
Issues
提交bug和新的改进需求
仓库作者:commit的时候指定 issue的标签,会同步到相应issue位置:commit "xxxxx#数字"
就可以关联了。
Pull Request
仓库作者
创建的新分支然后合并到远程仓库会触发pull request
此时本地主分支需要先
git pull
拉取远程分支,然后才能删除本地的子分支。其余用户
首先fork别人的仓库,先将别人的仓库克隆到本地,修改好后提交。然后提示会pull request提示,发送后源仓库作者就能收到pull request。
.gitignore文件——如何在Git中忽略文件和文件夹
.gitignore文件
在git中,所有被忽略的文件都会被保存在一个 .gitignore
文件中。
.gitignore
文件是一个纯文本文件,包含了项目中所有指定的文件和文件夹的列表,这些文件和文件夹是 Git 应该忽略和不追踪的。
在 .gitignore
中,你可以通过提及特定文件或文件夹的名称或模式来告诉 Git 只忽略一个文件或一个文件夹。你也可以用同样的方法告诉 Git 忽略多个文件或文件夹。
创建.gitignore文件
通常,一个 .gitignore
文件会被放在仓库的根目录下。根目录也被称为父目录和当前工作目录。根目录包含了组成项目的所有文件和其他文件夹。
也就是说,你可以把它放在版本库的任何文件夹中。你甚至可以有多个 .gitignore
文件
.gitignore文件内容
在Git中忽略一个文件或者文件夹
忽略与
.gitignore
同一目录下的特定名称文件1
/test.txt
根目录下的某一文件夹下的特定文件
1
/test/test.txt or test/test.txt
忽略所有具有特定名称的文件,需要写出该文件的字面名称
1
test.txt
忽略整个文件夹
1
test/
忽略特定名称文件
1
2
3
4test # 匹配该名字的文件或目录
img* # 忽略特定单词开头的文件或目录
*.md # 忽略以md结尾的文件
! text.md # 不忽略某个文件,对于忽略整个目录,此种方法无效
忽略以前提交的文件
首先需要更新
.gitignore
文件以包括.env
文件(需要忽略的文件),此处为命令行操作1
2给 .gitignore 添加 .env 文件
echo ".env" >> .gitignore告诉Git不要追踪这个文件,把它从索引中删除
1
git rm --cached .env
git rm
命令,连同--cached
选项,从版本库中删除文件,但不删除实际的文件。这意味着该文件仍然在你的本地系统和工作目录中作为一个被忽略的文件。用
git add
命令将.gitignore
添加到暂存区:1
git add .gitignore
用
git commit
命令提交.gitignore
文件:1
git commit -m "update ignored files"
补充内容
查看git文件树
tree .git
命令用于显示 .git
目录中的文件和子目录结构。.git
目录是一个隐藏目录,它包含 Git 仓库的所有元数据,包括配置文件、对象数据库、引用等。
查看git仓库的对象和内容
git cat-file
命令用于查看 Git 仓库中的对象内容。它可以用来查看 Git 对象(如提交、树、标签等)的详细信息。