跳转到内容

Git

git数据模型.svg

Git 的核心就是一个内容寻址的对象数据库(通过内容的哈希值来唯一识别和访问数据,而不是用名字或位置),有 3 种对象类型:

类型 用途
blob 代表文件的内容
tree 代表目录(以及其中的文件/子目录)
commit 代表一次提交

每个对象都有一个唯一的 SHA-1 哈希作为 ID

type object = blob | tree | commit

1. blob(Binary Large Object)

  • blob 用于保存文件内容的本身
  • 每个唯一内容的文件只会生成一个 blob
  • Git 不管文件名或路径,只看内容,内容变了就是新 blob
// blob 是一组数据
type blob = array<byte>

2. tree

  • tree 表示一个目录,记录目录下的所有文件(blob)和子目录(tree)的哈希值,定义了快照的结构和内容
  • 包含文件名、权限、对象类型、对象哈希
// tree 是一个包含文件和目录的目录
type tree = map<string, tree | blob>

3. commit

  • commit 是在提交时创建的对象
  • 包含 tree(项目快照) 的哈希值以及父提交(前一个提交)、作者信息、提交说明、提交时间等元数据
// commit 包含一个父提交,顶层树和元数据
type commit = struct {
parents: array<commit>
author: string
message: string
snapshot: tree
}

blob、tree、commit 层次关系

git对象层次关系.png

Git 在储存数据时,所有的对象都会基于它们的 SHA-1 哈希进行寻址

objects = map<string, object> // 哈希值 -> 对象
def store(object):
id = sha1(object)
objects[id] = object
def load(id)
return objects[id]

blob、tree 和 commit 都是对象,当它们引用其他对象时,它们并没有真正的在硬盘上保存这些对象,而是仅仅保存了它们的哈希值作为指针

所有的快照可以通过它们的 SHA-1 哈希值来标记,但这很不方便,毕竟谁也记不住一串 40 位的十六进制字符

  • 人能看懂名字:main、master、HEAD 等

  • Git 能看懂哈希:d8329fc1cc938780ffdd9f94e0d364e0ea74f579

引用(reference)是一个人类可读的名字,用来指向 Git 对象(通常是 commit 对象)的哈希,于是引用就可以把上述两者联系起来

references = map<string, string> // 名字 -> 哈希值
def update_reference(name, id):
references[name] = id
def read_reference(name):
return references[name]
def load_reference(name_or_id):
// 传入 name,先通过 references 去除 id 再加载
if name_or_id in references:
return load(references[name_or_id])
// 传入 id,直接加载
else:
return load(name_or_id)

1. 引用的作用

  • 管理分支:每个 branch 其实就是一个 ref

    分支是 ref 的一种,位于 refs/heads/ 目录下,它是一个可移动的指针,指向某个 commit,当提交新的 commit,分支指针会自动向前移动,指向最新提交

  • 管理标签:每个 tag 也是一个 ref

    标签也是 ref 的一种,位于 refs/tags/ 目录下,它是一个固定的指针,通常指向某个特定的 commit,标签一般用于给重要版本打标记,比如 v1.0、release-2023-08,标签一般不随新提交移动,是静态的

  • 管理当前状态:HEAD 是一个特殊的 ref,用来指示“当前在哪个分支”或“当前在哪个 commit 上”

2. 引用的位置

Git 把所有的引用保存在 .git/refs 目录下:

.git/
├── HEAD # 当前引用
├── refs/
│ ├── heads/ # 分支引用
│ │ ├── main
│ │ └── dev
│ └── tags/ # 标签引用
│ | └── v1.0
| ├── remotes/ # 远程分支引用
│ │ └── origin/
│ │ ├── main
│ │ └── dev
└── packed-refs # 被压缩的引用(很多时 Git 会打包)

Git 把工作状态分成了三个分区:

  • 工作区(Working Directory)
  • 暂存区(Staging Area)
  • 仓库区(Repository)

1. 工作区

工作区就是所有源文件目录,增加文件、修改文件首先发生在这个区域,文件在这个区的状态是红色的已修改(modified),通过 git status 可以看到哪些文件被修改了

2. 暂存区

使用 git add <file> 可以把文件从“工作区”放进“暂存区”,暂存区包含了当前想提交的文件内容的快照,但这个快照并不是直接拷贝整个文件,而是:

  • 记录了文件路径

  • 文件的执行权限

  • 文件对应的 blob 对象的哈希值

当被修改的文件加入暂存区后,状态会变成绿色的已修改(modified),它存储在 .git/index 文件中

3. 仓库区

使用 git commit 命令时,Git 会把“暂存区”的内容保存到本地仓库,仓库中记录的是 Git 对象(提交 commit、树 tree、文件 blob),这些对象组成了 Git 的历史,位于 .git/objects/ 中,是 Git 的核心数据

三大分区工作流程

基本的 Git 工作流程如下:

  1. 在工作区中修改文件
  2. 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区
  3. 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录
编辑文件
↓
工作区
↓ git add
暂存区
↓ git commit
仓库区

远端仓库是托管在网络服务器(如互联网、局域网)上的 Git 仓库,与本地仓库(开发者电脑上的仓库)相对应。它本质上是一个裸仓库(Bare Repository),即不包含工作区,仅存储版本控制所需的元数据(如提交历史、分支信息等),专门用于接收和分发代码,通常位于第三方托管平台。

这里的远端不一定表示仓库在网络上的其他位置,而只是表示仓库在别处,所以也可以在自己的本地主机上创建一个远端仓库, 在这样的远端仓库上工作,仍然需要和其它远端仓库上一样的标准推送、拉取和抓取操作。

1. 远程仓库作用

  • 代码备份:本地代码推送到远程仓库后,即使本地设备故障,也能从远端恢复代码。

  • 多人协作:团队成员通过远端仓库共享代码,避免直接传输文件的混乱(如邮件、U 盘传递)。

  • 版本集中管理:远端仓库保存完整的提交历史,所有成员可基于同一版本库协作,减少冲突。

  • 支持分支协作:远端仓库可托管多个分支(如main、develop),方便多人并行开发。

2. 常见的托管平台

远端仓库通常需要借助第三方平台托管,以下是主流选择:

平台 特点 适用场景
GitHub 全球最大的开源社区,支持公开 / 私有仓库,集成 CI/CD、Issue 管理等 开源项目、个人学习、团队协作
GitLab 支持自托管(可部署在企业内网),功能全面(代码审查、容器 registry 等) 企业级私有项目、对数据安全要求高的场景
Gitee(码云) 国内平台,访问速度快,支持中文,提供免费私有仓库 国内团队协作、对访问速度敏感的项目
Bitbucket 与 Atlassian 工具(Jira、Confluence)集成紧密,适合小型团队 与 Atlassian 生态结合的团队

Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量,Git 配置有三个层级,配置的优先级从低到高依次是:

层级 作用范围 配置文件位置
系统级(system) 适用于系统上所有用户 /etc/gitconfig(Linux/macOS)
全局级(global) 当前用户所有仓库 ~/.gitconfig 或 ~/.config/git/config
仓库级(local) 当前 Git 仓库 .git/config
  • 执行 git config 配置时可以指定配置层级
  • 每一个级别会覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量

常用命令:

查看所有配置:

Terminal window
git config --list

查看某个配置项:

Terminal window
git config user.name

设置用户名和邮箱:

Terminal window
git config --global user.name "name"
git config --global user.email "name@example.com"

设置默认编辑器:

Terminal window
git config --global core.editor "vim"

配置合并工具:

Terminal window
git config --global merge.tool meld

设置别名:

Terminal window
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit

用于查看 Git 命令的帮助信息

常见用法:

  • git help <verb>:获取 <verb> 命令的帮助
  • git <verb> --help:与 git help 等效
  • man git-<verb>:使用 man 手册查看命令帮助

用于将当前目录初始化为 Git 仓库,生成 .git 子目录,包含 Git 所需的必要文件

常见用法:

  • git init:初始化当前目录为仓库

用于显示当前工作目录和暂存区的状态,比如

  • 被修改但还未提交的文件
  • 已经准备好提交的文件
  • 新建但未被跟踪的文件
  • 当前所在的分支
  • 当前分支与远程分支的同步状态(是否落后或领先)

常见用法:

  • git status:显示完整状态
  • git status -s:显示简洁状态(简洁格式)

用于将指定文件的变更添加到暂存区,常用于:

  • 把未被跟踪的文件变为已跟踪状态
  • 更新已跟踪文件的快照

常见用法:

  • git add <filename>:将单个 filename 文件添加到暂存区
  • git add <directory>:将整个 directory 目录添加到暂存区
  • git add .:将该目录下所有文件添加到暂存区
  • git add -u:添加所有被跟踪文件的修改(不包括新文件,因为新文件还未被跟踪)
  • git add -p:交互式添加文件的部分修改

用于将暂存区的内容(即通过 git add 添加的变更)提交到 Git 仓库,形成一次新的提交记录

常见用法:

  • git commit:创建一次提交,打开编辑器编辑提交信息
  • git commit -m "<message>":直接附带提交信息,跳过编辑器
  • git commit -a -m "<message>":跳过暂存区,直接提交所有已跟踪文件的修改

参考如何编写 良好的提交信息

用于显示当前分支的提交历史,按时间从新到旧列出每一次提交,包括提交哈希、作者、时间、提交信息等

常见用法:

  • git log:显示完整的提交历史
  • git log <filename>:查看指定文件的提交历史
  • git log -<n>:显示最近 n 条提交
  • git log --oneline:显示提交历史,精简格式,单行显示
  • git log --oneline --graph --all --decorate:图形化查看提交历史

显示两个版本之间的内容差异

常见用法:

  • git diff:查看工作区(未暂存)和暂存区的差异

  • git diff --staged:查看暂存区和最新提交(HEAD)的差异

  • git diff HEAD:查看整个工作区(包括未暂存和暂存的)与最新提交的差异

  • git diff <branch1> <branch2>:查看两个分支之间的差异

  • git diff <commit1> <commit2>:查看两个提交之间的差异

  • git diff <tag1> <tag2>:查看两个标签之间的差异

用于 Git 中的分支管理,包括创建、查看、删除、重命名本地或远程分支等

常见用法:

  • git branch:查看所有本地分支

  • git branch -vv:查看所有分支及其对应的远程分支、最新提交、同步状态(是否 ahead/behind)

  • git branch <new-branch>:创建新分支(不会自动切换)

  • git branch -d <branch>:删除已合并的本地分支,如果分支未合并 Git 会阻止删除(安全删除)

  • git branch -D <branch>:会强制删除分支(不管是否已合并)

  • git branch -m <old-name> <new-name>:重命名分支

  • git branch -m <new-name> :重命名当前分支

  • git branch --set-upstream-to=<remote/branch> <branch>:将本地 branch 分支设置为跟踪远程 remote/branch 分支,方便后续使用 git pull 或 git push 时自动匹配远程目标

用于切换分支,或者将工作区的文件还原到某个提交或分支的状态

常见用法:

  • git checkout <branch>:切换到指定分支

  • git checkout -b <new-branch>:创建并切换到新分支(相当于 git branch + git checkout)

  • git checkout <commit>:将工作区还原到某个提交( HEAD 会处于游离状态)

  • git checkout -- <file>:丢弃工作区中对指定文件的修改,恢复为上一次提交的状态

  • git checkout <commit-hash> -- file.txt:丢弃工作区中对指定文件的修改,恢复为 commit-hash 指定的提交状态

用于将另一个分支的修改合并到当前分支,生成一次新的合并提交

常见用法:

  • git merge <branch>:将指定分支合并到当前分支
  • git merge --no-ff <branch>:强制创建一次合并提交,即使可以 fast-forward(便于保留分支历史)
  • git merge --abort:合并冲突未解决时,取消合并并回退到合并前的状态
  • git mergetool:在遇到合并冲突时,调用合并工具(如 VS Code、meld、vimdiff 等)来手动解决冲突

把前分支上从公共祖先之后的提交一个个取出来,重新应用(复制)到目标分支的最新提交上

常见用法:

  • git rebase <branch>:将当前分支的提交,变基到 <branch> 的最新提交上
  • git rebase -i <commit>:交互式变基,从指定提交之后开始,支持编辑、压缩、删除等操作(常用于整理提交历史)

用于管理远端仓库的引用,包括查看、添加、删除和修改远端仓库

常见用法:

  • git remote:列出所有远端仓库的简短名称(如 origin)
  • git remote -v:显示远端仓库的详细 URL 信息
  • git remote add <name> <url>:添加一个新的远端仓库,并命名为 <name>
  • git remote remove <name>:删除指定的远端仓库
  • git remote rename <old> <new>:重命名远端仓库
  • git remote show <name>:显示指定远端仓库的详细信息,包括跟踪的分支和推送权限

用于从远端仓库复制整个仓库到本地,包含所有分支和提交历史

常见用法:

  • git clone <url>:克隆远端仓库到当前目录,默认目录名为仓库名
  • git clone <url> <directory>:克隆远端仓库到指定目录
  • git clone --branch <branch> <url>:克隆指定分支
  • git clone --depth <n> <url>:浅克隆,只获取最近的 n 次提交,减少下载量

从远端仓库获取最新的更新,但不会自动合并到当前分支,只是把更新下载到本地仓库

常见用法:

  • git fetch:从默认远端仓库(如 origin)获取所有分支的更新
  • git fetch <remote>:从指定远端仓库获取更新
  • git fetch <remote> <branch>:只获取指定远端仓库的指定分支的更新

从远端仓库拉取最新的更改并自动合并到当前分支,等同于执行 git fetch + git merge

常见用法:

  • git pull:从默认远端仓库拉取当前分支的更新并合并
  • git pull <remote> <branch>:从指定远端仓库和分支拉取更新并合并
  • git pull --rebase:拉取更新后使用 rebase 代替 merge,保持提交历史整洁

将本地分支的提交推送到远端仓库,更新远端分支

常见用法:

  • git push:将当前分支推送到默认远端仓库对应的分支
  • git push <remote> <branch>:将本地分支推送到指定远端仓库的分支
  • git push -u <remote> <branch>:推送并设置本地分支跟踪指定远端分支(方便后续使用 git pull 或 git push)
  • git push <remote> <tagname>:将本地标签推送到远端仓库

用于给提交打标签,方便标记重要的版本

常见用法:

  • git tag:列出所有标签
  • git tag <tagname>:给当前提交创建一个轻量标签
  • git tag -a <tagname> -m "<message>":创建带注释的标签,并添加说明信息
  • git tag -d <tagname>:删除本地标签

撤销 & 停止追踪

  • git commit --amend:主要用途有两个
    • 修改最近一次提交的提交信息(比如改错字、补充说明)
    • 将暂存区的新修改合并进最近一次提交(不产生新的提交记录)
  • git reset HEAD <file>:将文件从暂存区移除,恢复为未暂存状态
  • git checkout -- <file>:丢弃工作区对指定文件的修改(恢复到暂存区或最新提交状态)
  • git rm --cached <filename>:停止 Git 跟踪,但保留本地文件(仅取消追踪文件)
  • git rm <filename>:删除文件并从版本控制中移除(删除文件并取消追踪)

.gitignore

.gitignore 文件用于告诉 Git 哪些文件或目录应该被忽略,不纳入版本控制,能帮助避免提交临时文件、编译产物、日志文件等不需要跟踪的内容

.gitignore 文件格式与规则:

  • 每一行写一个模式
  • 空行或以 # 开头的行表示注释
  • 支持通配符:
    • * 匹配任意字符
    • ? 匹配单个字符
    • ** 匹配任意目录层级
  • 以 / 开头表示相对仓库根目录匹配
  • 以 / 结尾表示目录

示例:

Terminal window
# 忽略所有 .log 文件
*.log
# 忽略 node_modules 目录
node_modules/
# 忽略所有 .DS_Store 文件(macOS系统文件)
.DS_Store
# 忽略所有临时文件
*.tmp
# 忽略 dist 目录
/dist/
# 忽略所有以 ~ 结尾的文件(编辑器临时文件)
*~
# 忽略 config.json 文件(通常是包含配置信息的文件)
config.json