初始化仓库
创建一个新的Git仓库,开始版本控制之旅。
描述:在指定目录或当前目录初始化Git仓库
git init
# 创建并初始化新目录
git init my-project
添加文件到暂存区
将文件更改添加到Git的暂存区,准备提交。
描述:将文件或目录添加到暂存区
git add .
# 添加特定文件
git add app.js
# 添加多个文件
git add app.js styles.css
# 添加所有txt文件
git add *.txt
git add -p可以交互式地选择要添加的更改部分。
提交更改
将暂存区的更改永久保存到Git历史中。
描述:提交暂存区的更改
git commit -m "Initial commit"
# 跳过暂存区直接提交
git commit -am "Fix login bug"
# 修改最后一次提交
git commit --amend -m "Updated message"
feat: 添加新功能。
查看状态
查看工作区和暂存区的当前状态。
描述:显示工作区状态
git status
# 查看简洁状态
git status -s
# 查看分支状态
git status -sb
查看历史
查看项目的提交历史记录。
描述:查看提交历史
git log
# 查看简洁历史
git log --oneline
# 查看图形化历史
git log --graph --oneline
# 查看特定文件历史
git log --follow file.js
q退出日志查看。现代Git日志输出更加美观和直观。
Git 适用场景
了解哪些情况下适合使用Git进行版本控制。
软件开发项目
Git最核心的应用场景,管理源代码的版本迭代。
✅ 移动应用开发
✅ 桌面应用开发
✅ 游戏开发
✅ API服务开发
文档与写作
协作写作和文档版本管理,追踪每次修改。
✅ 学术论文(LaTeX)
✅ 书籍写作
✅ 博客文章
✅ 法律合同文本
配置文件管理
管理服务器配置、系统设置等文件的变更。
✅ Docker/Kubernetes配置
✅ CI/CD流水线配置
✅ 云基础设施代码(Terraform)
✅ 系统脚本和工具
设计资源管理
管理设计文件和创意资源(配合Git LFS)。
✅ 图标库
✅ 字体文件
✅ 设计规范文档
✅ 品牌资产
数据科学与研究
管理研究代码、实验配置和分析脚本。
✅ 机器学习模型代码
✅ 实验配置文件
✅ 研究项目代码
✅ 可复现性研究代码
教育与学习
课程项目、学习笔记和教学材料的版本管理。
✅ 学习笔记
✅ 教学课件
✅ 编程练习
✅ 开源学习项目
Git 不适用场景
了解哪些情况下不适合使用Git,避免误用。
大型二进制文件
Git不擅长处理大型二进制文件,会导致仓库体积膨胀。
❌ 视频文件(MP4、AVI等)
❌ 音频文件(WAV、FLAC等)
❌ 压缩包(ZIP、RAR、7z)
❌ 安装包(EXE、DMG、APK)
建议:使用Git LFS、外部存储服务(AWS S3、阿里云OSS)或专门的媒体管理系统。
频繁变化的临时文件
临时文件、缓存文件等不需要版本控制的内容。
❌ IDE配置(.idea、.vscode临时文件)
❌ 操作系统文件(.DS_Store、Thumbs.db)
❌ 日志文件(*.log)
❌ 数据库文件(SQLite、临时备份)
建议:使用.gitignore文件忽略这些文件。
敏感与机密数据
绝不应该将敏感信息提交到Git仓库。
❌ 数据库密码
❌ SSH私钥
❌ SSL证书
❌ 个人身份信息(PII)
建议:使用环境变量、密钥管理系统(HashiCorp Vault)或配置管理工具。
实时协作的富媒体编辑
需要多人同时在线编辑的富媒体文档。
❌ 复杂的Excel表格协作
❌ 在线白板绘图
❌ 视频剪辑项目
❌ 音频混音工程
建议:使用专门的协作工具(Google Docs、Notion、Figma、Miro等)。
极高频率的自动更新
每分钟甚至每秒都在变化的动态数据。
❌ 日志流(每秒写入)
❌ 交易记录
❌ 用户行为追踪数据
❌ 动态生成的报表
建议:使用数据库、时间序列数据库或日志收集系统。
受版权限制的付费内容
有版权限制或需要许可证的文件。
❌ 商业图片库素材
❌ 付费软件包
❌ 音乐版权文件
❌ 授权插件
建议:使用外部存储服务,在代码中引用而非包含文件本身。
非结构化的大数据集
海量的非结构化数据不适合Git管理。
❌ 数据库备份
❌ 影像库
❌ 备份归档
❌ 历史日志归档
建议:使用数据存储服务(HDFS、S3)、数据湖或专门的备份系统。
GUI 图形界面工具
图形化Git客户端,让版本控制更加直观便捷。
Sourcetree
免费且功能强大的Git客户端,支持Windows和Mac。
GitHub Desktop
GitHub官方桌面客户端,专为GitHub用户设计。
lazygit
终端里的Git UI工具,轻量级且高效。
✅ 终端界面
✅ 键盘操作
✅ 资源占用极低
✅ 支持自定义
安装:brew install lazygit
国外代码托管平台
主流的海外在线Git代码托管服务。
GitHub
全球最大的开源代码托管平台。
✅ 全球最大开源社区
✅ GitHub Actions CI/CD
✅ Pull Request功能
✅ GitHub Pages静态托管
✅ Copilot AI编程助手
✅ GitHub Packages
官网:github.com
GitLab
一站式DevOps平台,支持自托管。
Bitbucket
Atlassian旗下的代码托管服务。
Azure DevOps
微软的企业级DevOps平台。
✅ Azure Repos代码托管
✅ Azure Pipelines CI/CD
✅ Azure Boards项目管理
✅ 企业级安全合规
✅ Azure Test Plans
官网:azure.microsoft.com
国内代码托管平台
国内常用的代码托管服务,访问速度快,生态完善。
Gitee (码云)
国内领先的代码托管平台,访问速度快。
GitHub 中国镜像
GitHub的国内镜像服务,提升访问速度。
✅ hub.fastgit.xyz
✅ ghproxy.com
✅ mirror.ghproxy.com
使用方法:
git clone https://hub.fastgit.xyz/user/repo.git
开源中国 Gitee
国内最大的开源社区代码托管平台。
自托管Git服务
可以部署在自有服务器上的Git托管解决方案。
Gogs
轻量级的自托管Git服务,完全开源免费。
✅ 极其轻量(资源占用少)
✅ 易于安装和配置
✅ 支持MySQL、PostgreSQL、SQLite
✅ 中文界面
✅ 支持Markdown和WiKi
✅ Webhooks和Git钩子
GitHub:gogs/gogs
Gitea
Gogs的社区fork,功能更丰富。
GitLab CE/EE
GitLab开源版,可完全自托管。
Forgejo
从Gitea社区分叉的轻量级Git服务。
搭建推荐
个人/小团队
→ Gogs 或 Forgejo(轻量)
中等规模团队
→ Gitea(功能丰富)
企业级需求
→ GitLab CE/EE(功能全面)
Docker快速部署
docker run -d --name gitea -p 3000:3000 -p 2222:22 -v gitea:/data gitea/gitea:latest
查看分支
查看本地和远程的所有分支。
描述:列出、创建或删除分支
git branch
# 查看所有分支(包括远程)
git branch -a
# 查看远程分支
git branch -r
# 查看分支详细信息
git branch -vv
创建分支
创建新的开发分支。
描述:创建新分支
git branch feature-login
# 从指定提交创建分支
git branch feature-login abc123
# 创建并切换到新分支
git checkout -b feature-login
feature/、bugfix/前缀。
切换分支
在不同的分支之间切换。
描述:切换到指定分支
git checkout feature-login
# 使用switch命令(Git 2.23+)
git switch feature-login
# 切换到上一个分支
git checkout -
git stash保存未提交的更改。
合并分支
将一个分支的更改合并到当前分支。
描述:合并指定分支到当前分支
git merge feature-login
# 禁用快进合并
git merge --no-ff feature-login
# 中断合并
git merge --abort
删除分支
删除不再需要的分支。
描述:删除分支
git branch -d feature-login
# 强制删除分支
git branch -D feature-login
# 删除远程分支
git push origin --delete feature-login
-d只删除已合并的分支,-D可以删除未合并的分支(谨慎使用)。
添加远程仓库
关联本地仓库到远程仓库。
描述:添加远程仓库
git remote add origin https://github.com/user/repo.git
# 查看远程仓库
git remote -v
# 修改远程仓库URL
git remote set-url origin https://github.com/user/new-repo.git
克隆仓库
从远程仓库克隆到本地。
描述:克隆远程仓库
git clone https://github.com/user/repo.git
# 克隆到指定目录
git clone https://github.com/user/repo.git my-project
# 克隆特定分支
git clone -b develop https://github.com/user/repo.git
推送代码
将本地提交推送到远程仓库。
描述:推送本地分支到远程
git push origin main
# 推送并设置上游分支
git push -u origin main
# 推送所有分支
git push --all origin
# 强制推送(谨慎使用)
git push --force origin main
-u参数设置上游分支后,后续只需执行git push。
拉取代码
从远程仓库拉取最新代码并合并。
描述:拉取并合并远程更新
git pull origin main
# 拉取并变基
git pull --rebase origin main
# 只拉取不合并
git fetch origin main
git pull等同于git fetch + git merge。
获取更新
从远程仓库获取最新更新但不合并。
描述:获取远程更新
git fetch
# 获取特定远程仓库
git fetch origin
# 获取特定分支
git fetch origin main
暂存工作
临时保存当前工作区的更改。
描述:暂存当前工作区的更改
git stash
# 带消息暂存
git stash save "work in progress"
# 恢复最近暂存
git stash pop
# 查看暂存列表
git stash list
撤销提交
撤销或修改提交历史。
描述:重置当前分支到指定状态
git reset HEAD~1
# 撤销提交并删除更改
git reset --hard HEAD~1
# 修改最后一次提交
git commit --amend
--soft保留更改在暂存区,--mixed保留更改在工作区,--hard完全删除。
变基操作
重新应用提交到另一个基础。
描述:变基操作,线性化提交历史
git rebase main
# 交互式变基
git rebase -i HEAD~3
# 跳过变基
git rebase --skip
# 中止变基
git rebase --abort
选择提交
选择特定提交应用到当前分支。
描述:选择特定提交应用
git cherry-pick abc123
# 选择多个提交
git cherry-pick abc123..def456
# 选择但不自动提交
git cherry-pick -n abc123
git log查看。
AI 生成提交信息
使用AI工具分析代码变更,生成符合规范的提交信息。
描述:AI辅助生成提交信息
git add .
git commit -m "$(gh copilot suggest '生成描述这些变更的提交信息')"
# Gitmoji AI 助手
git commit -m "$(gitmoji -ai)"
# 自定义AI脚本
git commit -m "$(python ai-commit-generator.py)"
AI 代码审查
使用AI分析Pull Request,提供代码质量建议。
描述:AI辅助代码审查
# AI会自动检测潜在的bug、性能问题和安全漏洞
# GitLab Duo AI 审查
gitlab-duo review feature/new-ui
# 本地AI审查工具
ai-review --diff HEAD~1
AI 冲突解决
AI辅助理解和解决Git合并冲突。
描述:AI辅助冲突解决
git conflict explain
# AI 建议合并策略
git merge-suggest feature/new-ui
# AI 自动解决简单冲突
git resolve-conflict --ai file.js
AI 命令预测
AI根据上下文预测下一个Git命令。
描述:AI预测下一个命令
$ git [Tab]
# AI提示:根据您的更改,可能需要:
# git add modified-file.js
# git commit -m "fix: 修复登录验证逻辑"
# git push origin feature/login
# 配置shell集成
eval "$(git-predict init)"
GPG 签名验证
使用GPG/SSH密钥签名提交,验证作者身份。
描述:启用GPG签名验证
git config --global commit.gpgsign true
git config --global gpg.program gpg2
# 查看签名状态
git log --show-signature
# 使用SSH签名
git config --global commit.gpgsign true
git config --global gpg.format ssh
Conventional Commits
遵循规范化的提交信息格式。
描述:规范化提交信息格式
git commit -m "feat(auth): 添加OAuth2登录支持"
git commit -m "fix(api): 修复用户数据获取bug"
git commit -m "docs(readme): 更新安装说明"
git commit -m "style(button): 优化按钮样式"
# 安装commitlint
npm install @commitlint/cli @commitlint/config-conventional
Partial Clone
部分克隆大型仓库,节省时间和空间。
描述:部分克隆大型仓库
git clone --filter=blob:none https://github.com/user/repo.git
# 仅克隆特定分支
git clone --single-branch --branch main repo.git
# 克隆特定文件
git clone --depth 1 --filter=blob:limit=1m repo.git
Sparse Checkout
只检出仓库的特定目录。
描述:稀疏检出特定目录
git clone --no-checkout repo.git
cd repo
git sparse-checkout init
git sparse-checkout set src/api docs
# 添加更多目录
git sparse-checkout add src/components
# 禁用稀疏检出
git sparse-checkout disable
性能优化
优化大型仓库的性能。
描述:优化Git性能
git config --global core.parallelIndex 8
# 优化大文件处理
git lfs install
git lfs track "*.psd"
# 使用Git 2.50+的新索引格式
git update-index --index-version 4
# 清理无用文件
git gc --prune=now
日常工作流
程序员日常开发中最常用的Git操作流程。
标准开发流程
git pull origin main
# 2. 创建功能分支
git checkout -b feature/new-feature
# 3. 开发并查看状态
git status
# 4. 添加更改到暂存区
git add .
# 5. 提交代码
git commit -m "feat: 添加新功能"
# 6. 推送到远程
git push -u origin feature/new-feature
# 7. 创建Pull Request/Merge Request
提交前检查
git diff --cached
# 查看未暂存的更改
git diff
修复Bug流程
发现Bug后如何使用Git进行修复。
git checkout main
git pull origin main
git checkout -b bugfix/login-error
# 2. 定位问题提交
git log --oneline --all
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# 3. 修复代码并提交
git add .
git commit -m "fix: 修复登录验证错误"
# 4. 推送并创建PR
git push -u origin bugfix/login-error
查看变更历史
git log --follow -p filename.js
# 查看谁在什么时候修改的
git blame filename.js
紧急修复
生产环境紧急问题的快速处理流程。
git checkout -b hotfix/critical-bug v1.0.0
# 2. 快速修复问题
git add .
git commit -m "hotfix: 修复生产环境严重错误"
# 3. 打补丁标签
git tag -a v1.0.1 -m "Hotfix patch"
# 4. 合并回main和develop
git checkout main
git merge --no-ff hotfix/critical-bug
git tag v1.0.1
# 5. 推送所有
git push origin main --tags
常用别名
配置常用命令别名,提高工作效率。
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# 使用别名
git st
git co feature/login
git br -a
git ci -m "fix bug"
git lg
忽略文件
配置 .gitignore 忽略不需要版本控制的文件。
# 依赖目录
node_modules/
vendor/
# 编译输出
dist/
build/
*.min.js
# 环境配置
.env
.env.local
# IDE 配置
.vscode/
.idea/
# 日志文件
*.log
npm-debug.log*
# 操作系统文件
.DS_Store
Thumbs.db
# 忽略所有 .txt 文件
*.txt
# 但保留 important.txt
!important.txt
常用命令
git ls-files --others --ignored --exclude-standard
# 从版本控制中移除已跟踪文件(保留本地)
git rm --cached filename
# 移除目录下所有已跟踪文件
git rm -r --cached directory/
存储结构
深入了解Git的底层存储架构和目录结构。
.git 目录结构
├── HEAD # 当前分支引用
├── config # 仓库配置文件
├── description # 仓库描述
├── hooks/ # Git钩子脚本
│ ├── pre-commit
│ ├── post-commit
│ └── ...
├── info/ # 排除文件
│ └── exclude
├── objects/ # Git对象存储
│ ├── info/ # 包文件信息
│ │ └── packs
│ └── pack/ # 压缩包文件
│ ├── pack-*.idx # 索引文件
│ └── pack-*.pack # 数据文件
└── refs/ # 引用存储
├── heads/ # 本地分支
│ ├── main
│ └── develop
├── tags/ # 标签
│ ├── v1.0.0
│ └── v1.1.0
└── remotes/ # 远程引用
└── origin/
├── main
└── develop
核心目录详解
objects/ - 存储所有Git对象,使用前2位哈希作为目录名,剩余38位作为文件名
refs/ - 存储分支、标签等引用,内容是commit哈希
HEAD - 指向当前分支的符号引用
index - 暂存区二进制文件(隐藏)
config - 仓库特定配置(用户名、邮箱、远程仓库等)
hooks/ - 自定义脚本,在特定Git操作时自动执行
Git对象存储结构图
objects/
├── ab/ # 哈希前2位作为目录
│ └── cdef1234... # 剩余38位作为文件名
├── de/
│ └── f4567890...
└── pack/ # 打包存储(优化)
├── abc123.idx # 索引文件
└── abc123.pack # 数据文件
查看内部结构
ls -la .git/objects/
# 查看HEAD内容
cat .git/HEAD
# 查看分支引用
cat .git/refs/heads/main
# 查看所有对象
git rev-list --all --objects
# 查看对象大小
git count-objects -vH
对象模型
Git的四种核心对象类型及其作用。
四种Git对象
# 文件内容被压缩并计算SHA-1哈希
# 相同内容只存储一次(去重)
# 不包含文件名、路径等信息
# 2. Tree 对象 - 类似目录
# 记录文件名、权限和blob/tree引用
# 递归形成文件系统树
# 一个tree可以包含blob和其他tree
# 3. Commit 对象 - 提交记录
# 包含:tree引用、父commit、作者、时间、消息
# 形成提交链(DAG - 有向无环图)
# 4. Tag 对象 - 标签
# 指向特定commit,包含签名和消息
# 用于版本标记,可以是轻量级或带注释
对象关系图
Commit (HEAD)
┌─────────────────────────────────┐
│ tree: abc123... │
│ parent: def456... │
│ author: 张三 <zhang@example.com> │
│ date: 2024-01-15 10:30:00 │
│ message: "添加新功能" │
└─────────────────────────────────┘
│
▼
Tree (abc123...)
┌─────────────────────────────────┐
│ 100644 blob 789abc... README.md│
│ 100644 blob 456def... main.js │
│ 040000 tree 123456... src/ │
└─────────────────────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
Blob Blob Tree
(README) (main.js) (src/)
│
▼
Tree (123456...)
┌─────────────────────────────────┐
│ 100644 blob 789012... app.js │
└─────────────────────────────────┘
│
▼
Blob (app.js)
查看对象
git cat-file -t <hash>
# 查看对象内容
git cat-file -p <hash>
# 查看对象大小
git cat-file -s <hash>
# 查看所有对象
git rev-list --all --objects
# 查看对象的原始数据
git cat-file <hash>
哈希计算原理
# 示例:内容为"hello"的blob对象
# Header: "blob 5\0hello"
# SHA1("blob 5\0hello") = a0423896b8...
# 这保证:相同内容 = 相同哈希
# 不同类型 = 不同哈希
# 即使内容相同,类型不同,哈希也不同
对象不可变性
Git对象一旦创建就不可修改。修改内容会创建新的对象,新的哈希。这就是为什么Git操作(如修改提交)实际上是创建新对象。
索引机制
理解暂存区(index)的工作原理。
什么是索引
索引(Index)是一个二进制文件,位于 .git/index,记录了暂存区的文件信息。它是工作区和提交之间的桥梁。
索引结构图
┌─────────────────────────────────────┐
│ 工作区 (Working Dir) │
│ ┌──────────┐ ┌──────────┐ │
│ │file1.txt │ │file2.txt │ │
│ │ (已修改) │ │ (未修改) │ │
│ └──────────┘ └──────────┘ │
└──────────────┬──────────────────────┘
│ git add
▼
┌─────────────────────────────────────┐
│ 索引 (.git/index) │
│ ┌──────────────────────────────┐ │
│ │ file1.txt → blob: abc123... │ │
│ │ file2.txt → blob: def456... │ │
│ │ (按路径排序,支持快速查找) │ │
│ └──────────────────────────────┘ │
└──────────────┬──────────────────────┘
│ git commit
▼
┌─────────────────────────────────────┐
│ 提交 (Commit) │
│ ┌──────────────────────────────┐ │
│ │ tree: xyz789... │ │
│ │ parent: ... │ │
│ │ message: "..." │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
索引条目结构
# - ctime/mtime: 文件修改时间
# - dev/ino: 文件设备/索引节点
# - mode: 文件权限 (100644/100755/120000)
# - uid/gid: 用户/组ID
# - size: 文件大小
# - object_id: blob对象哈希
# - flags: 状态标志
# - path: 文件路径
# 索引按路径排序,支持二分查找
查看索引
git ls-files -s
# 查看索引详细信息
git ls-files --stage
# 查看暂存区文件状态
git diff --cached
# 查看索引统计信息
git ls-files --debug
# 查看索引的二进制内容
git ls-files --debug --format='%(objectname) %(path)'
git add 原理
# 1. 读取工作区文件内容
# 2. 计算内容的SHA-1哈希
# 3. 在objects目录中查找该哈希
# 4. 如果对象不存在,创建blob对象并存储
# 5. 更新索引,添加/更新文件条目
# 这就是为什么"添加"很快:
# - 内容未变则复用已有对象
# - 只更新索引,不立即提交
# - 增量更新,不复制整个文件
索引状态
# 1. 未跟踪 (untracked)
# - 新文件,从未被add
# 2. 已暂存 (staged)
# - 在索引中,等待提交
# 3. 已修改 (modified)
# - 工作区与索引不一致
# 查看状态:
git status
引用机制
理解Git如何通过引用定位对象和分支。
引用类型
# .git/HEAD 内容: ref: refs/heads/main
# 2. 分支引用 - refs/heads/
# .git/refs/heads/main 内容: abc123... (commit哈希)
# 3. 远程引用 - refs/remotes/
# .git/refs/remotes/origin/main 内容: def456...
# 4. 标签引用 - refs/tags/
# .git/refs/tags/v1.0 内容: 789abc...
# 5. 特殊引用 - refs/stash, refs/original/*
引用关系图
┌─────────────────────────────────────┐
│ HEAD │
│ ref: refs/heads/main │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ refs/heads/main (分支) │
│ abc123def... │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Commit │
│ tree: xyz789... │
│ parent: (上一提交) │
│ message: "..." │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ refs/tags/v1.0 (标签) │
│ abc123def... │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ refs/remotes/origin/main (远程) │
│ def456ghi... │
└─────────────────────────────────────┘
引用解析
git show-ref
# 查看特定引用
git show-ref heads/main
# 查看HEAD指向
git symbolic-ref HEAD
# 查看引用日志
git reflog show main
# 解析引用到commit
git rev-parse main
detached HEAD 状态
当HEAD直接指向commit而非分支引用时,称为detached HEAD状态。
正常状态:
HEAD → refs/heads/main → abc123... (commit)
Detached HEAD:
HEAD → abc123... (commit 直接指向)
git checkout abc123def
# 查看当前HEAD状态
cat .git/HEAD
# 从detached状态恢复
git checkout main
# 在detached状态下创建新分支
git checkout -b new-branch
轻量级标签 vs 带注释标签
git tag v1.0
# .git/refs/tags/v1.0 内容: abc123...
# 带注释标签(指向tag对象)
git tag -a v1.0 -m "版本1.0"
# .git/refs/tags/v1.0 内容: def456... (tag对象)
# 查看标签类型
git cat-file -t refs/tags/v1.0
命令原理
深入理解常用Git命令的底层实现。
git commit 原理
# 1. 基于索引创建tree对象
# 2. 读取当前HEAD获取父commit
# 3. 创建commit对象(包含tree、父commit、作者、消息)
# 4. 计算commit对象的SHA-1哈希
# 5. 更新当前分支引用指向新commit
# 验证commit内容:
git cat-file -p HEAD
# 查看commit的tree:
git cat-file -p HEAD | grep tree | awk '{print $2}' | xargs git cat-file -p
git merge 原理
# 1. 找到两个分支的共同祖先(LCA)
# 2. 比较分支与祖先的差异(三方差异)
# 3. 使用祖先作为基准进行三方合并
# 4. 如果无冲突,创建merge commit(两个父commit)
# 三方合并算法:
# - 两个分支修改了不同区域 → 自动合并
# - 只有一个分支修改 → 采用该修改
# - 两个分支修改相同区域 → 产生冲突
三方合并示意图
A (共同祖先)
/ \
/ \
B C (两个分支)
\ /
\ /
D (merge commit)
三方合并:
- 以A为基准
- 比较A→B的变更
- 比较A→C的变更
- 合并到D
git checkout 原理
# 1. 更新HEAD引用指向目标分支
# 2. 读取目标commit的tree对象
# 3. 递归遍历tree,用blob更新工作区文件
# 4. 更新索引匹配新的tree
# 5. 删除工作区中不存在于新tree的文件
# 这就是为什么切换分支会改变文件!
# checkout本质是:更新HEAD + 恢复工作区 + 更新索引
git rebase 原理
# 1. 找到当前分支和目标分支的共同祖先
# 2. 提取当前分支从祖先开始的所有commit
# 3. 暂存这些commit的变更
# 4. 将当前分支重置到目标分支
# 5. 在目标分支上依次重新应用每个commit
# 6. 为每个commit创建新的commit(新哈希)
# rebase vs merge:
# merge: 保留完整历史,有分支图
# rebase: 线性历史,重写提交哈希
rebase 示意图
Rebase 前: Rebase 后:
A A
/ \ |
B C C
/ \ |
D E D'
/ /|
F F' E'
(B和C是共同祖先后的分支)
(D', E', F'是重新应用的commit)
git diff 原理
# 比较工作区文件和索引中的blob对象
# git diff --cached(暂存区 vs HEAD)
# 比较索引和HEAD的tree对象
# git diff branch1 branch2
# 比较两个commit的tree对象
# 使用的是差异算法(如Myers算法)
# 找出最小的编辑序列将A转换为B
git pull 原理
# 执行步骤:
# 1. git fetch: 从远程拉取更新
# - 更新 refs/remotes/origin/*
# - 不修改工作区
# 2. git merge: 合并远程更新
# - 默认使用快进合并
# - 或创建merge commit
# git pull --rebase = git fetch + git rebase
git push 原理
# 1. 打包本地objects
# 2. 发送到远程仓库
# 3. 更新远程引用(refs/heads/*)
# 快进推送:
# 本地提交是远程提交的后继
# 强制推送:
# 覆盖远程历史(谨慎使用)
Git 比较算法
深入了解Git如何计算文件和目录之间的差异。
Myers 差异算法
Git使用Myers差异算法来计算两个文本之间的最小编辑序列,这是最经典的diff算法之一。
算法核心思想
通过构建一个编辑图(Edit Graph),寻找从起点到终点的最短路径。每条路径代表一种编辑操作(插入、删除、匹配)。
编辑图示例:
文件A: ABCAB
文件B: ACBBC
C B B C
┌───┬───┬───┬───┐
A │ × │ \ │ × │ \ │ (×=删除, \=对角线=匹配)
├───┼───┼───┼───┤
B │ \ │ × │ \ │ × │
├───┼───┼───┼───┤
C │ × │ \ │ × │ \ │
├───┼───┼───┼───┤
A │ \ │ × │ \ │ × │
├───┼───┼───┼───┤
B │ × │ \ │ × │ \ │
└───┴───┴───┴───┘
最短路径 = 最少编辑操作
时间复杂度
# N = 文件A的行数
# D = 最小编辑距离
# 实际应用中:通常接近O(N+M)
# M = 文件B的行数
# Git优化:使用二分查找和启发式加速
Patience Diff 算法
Git 2.11+引入的另一种diff算法,在某些场景下比Myers算法更符合人类直觉。
算法原理
寻找文件中"独特且有序"的共同行(unique common elements),作为锚点进行匹配。
文件A: 文件B:
1. import A 1. import A ← 锚点(唯一且顺序一致)
2. function foo 2. function bar
3. const x = 1 3. const x = 1 ← 锚点
4. function bar 4. function foo
5. const y = 2 5. const y = 2 ← 锚点
6. export B 6. export B ← 锚点
Patience Diff识别锚点后,在锚点之间使用Myers算法
适用场景
✅ 大量相似的重复代码
✅ 结构性重构
❌ 纯文本替换
❌ 微小的局部修改
Histogram Diff 算法
Git默认的diff算法,结合了Myers和Patience的优点,使用直方图统计来优化匹配。
算法特点
# 2. 优先匹配出现次数少的行(更可能是锚点)
# 3. 在锚点之间使用Myers算法
# 4. 对锚点附近的行进行局部优化
# 优势:
# - 比纯Myers更符合人类直觉
# - 比Patience diff适用范围更广
# - 性能优化更好
diff 算法对比
| 算法 | 速度 | 准确性 | 可读性 | 使用场景 |
|---|---|---|---|---|
| Myers | ★★★☆☆ | ★★★★☆ | ★★★☆☆ | 通用,Git默认 |
| Patience | ★★☆☆☆ | ★★★★★ | ★★★★★ | 代码重排 |
| Histogram | ★★★★☆ | ★★★★☆ | ★★★★☆ | 现代Git默认 |
| Minimal | ★☆☆☆☆ | ★★★★★ | ★★★☆☆ | 精确分析 |
切换 diff 算法
git diff --patience
# 使用Histogram diff(默认)
git diff --histogram
# 使用Myers diff
git diff --minimal
# 全局配置默认算法
git config --global diff.algorithm histogram
# 仓库级别配置
git config diff.algorithm patience
diff 优化技巧
git diff --ignore-space-at-eol
git diff --ignore-space-change
git diff --ignore-all-space
# 单词级别的diff
git diff --word-diff
git diff --word-diff=color
# 限制上下文行数
git diff -U3 # 只显示3行上下文
# 忽略特定目录的diff
git diff -- . ':!node_modules'
# 使用外部diff工具
git difftool --tool=vimdiff
分支合并原理
深入理解Git如何合并不同分支的更改。
合并基础概念
Git合并的核心是找到共同祖先(Common Ancestor),然后进行三方合并(3-way merge)。
┌─────────────────────────────────────┐
│ 合并前状态 │
└─────────────────────────────────────┘
A (共同祖先)
/ \
分支1: B C 分支2
| |
D E
合并 main ← feature:
找到共同祖先: A
合并 B→D 和 C→E 的变更
结果: 新的merge commit
三方合并算法
Git使用递归三方合并(Recursive 3-way merge)算法,这是最常用的合并策略。
合并步骤
# 2. 比较 祖先→分支1 的变更(diff1)
# 3. 比较 祖先→分支2 的变更(diff2)
# 4. 逐文件合并:
# - 如果只在diff1中修改 → 采用diff1
# - 如果只在diff2中修改 → 采用diff2
# - 如果都修改了同一行 → 产生冲突
# - 如果都修改了不同行 → 自动合并
# 5. 创建merge commit(两个父commit)
合并示例
共同祖先 (A):
const x = 1;
const y = 2;
const z = 3;
分支1 (B): 分支2 (C): 合并结果:
const x = 10; const x = 1; const x = 10; ← 冲突!
const y = 20; const y = 2; const y = 20; ← 冲突!
const z = 3; const z = 30; const z = 30; ← 自动合并
冲突标记:
<<<<<<< HEAD
const x = 10;
const y = 20;
=======
const x = 1;
const y = 2;
>>>>>>> feature
递归合并策略
当存在多个共同祖先时,Git使用递归合并策略。
递归合并原理
多个共同祖先的情况:
A (共同祖先1)
/ \
B C
/ \ / \
D E F (两个共同祖先: D, F)
\ / /
G H
\ /
I (合并点)
递归合并步骤:
1. 找到所有共同祖先: [D, F]
2. 合并 D 和 F (虚拟祖先)
3. 使用虚拟祖先进行三方合并
优势:
- 处理复杂的交叉合并
- 减少冲突
- 更智能的合并决策
合并策略类型
| 策略 | 命令 | 描述 | 适用场景 |
|---|---|---|---|
| recursive | git merge |
递归三方合并(默认) | 大多数场景 |
| resolve | -s resolve |
简单的三方合并 | 快速合并,冲突少 |
| octopus | 自动选择 | 合并多个分支 | 合并3+分支 |
| ours | -s ours |
保留当前分支 | 丢弃其他分支 |
| subtree | -s subtree |
子树合并 | 子模块替代 |
冲突解决原理
当两个分支修改了同一行内容时,Git无法自动决定采用哪个版本,产生冲突。
冲突标记格式
当前分支的内容
======= (分隔符)
合并分支的内容
>>>>>>> feature (合并分支的修改)
# 解决冲突后运行:
git add
git commit
冲突类型
# - 两个分支修改了同一行
# - 相邻的修改导致无法自动合并
# 2. 树冲突
# - 一个分支重命名文件,另一个修改
# - 一个分支删除文件,另一个修改
# 3. 二进制冲突
# - 二进制文件无法diff
# - 需要手动选择版本
合并冲突检测优化
git config --global merge.conflictstyle diff3
# diff3 风格包含共同祖先内容
<<<<<<< HEAD
当前版本
||||||| merged common ancestors
共同祖先版本
=======
合并版本
>>>>>>> feature
# 使用外部合并工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
快进合并 (Fast-forward)
当目标分支是当前分支的直接后继时,可以使用快进合并。
快进合并前: 快进合并后:
main: A ← B main: A ← B ← C
↑
feature:
A ← B ← C
条件: feature 是 main 的直接后继
结果: 简单地移动指针,无额外commit
禁用快进:
git merge --no-ff feature
保留完整历史:
A ← B ← C ← D (merge commit)
\_____/
git merge feature
# 禁用快进合并
git merge --no-ff feature
# 仅快进合并
git merge --ff-only feature
Rebase 合并 vs Merge 合并
Merge 合并:
A---B---C---D (main)
\ \
E---F---G (merge)
优点: 保留完整历史
缺点: 历史图复杂
Rebase 合并:
A---B---C---D (main)
\
E'---F' (rebase后)
优点: 线性历史,简洁
缺点: 重写历史,改变commit哈希
git merge feature
# Rebase: 线性化历史
git rebase main
git merge feature
合并性能优化
git merge --strategy-option patience
# 跳过冲突文件的自动合并
git merge --strategy-option theirs
# 使用稀疏合并(大仓库)
git sparse-checkout init
git sparse-checkout set src
# 并行合并(Git 2.27+)
git -c merge.parallelism=8 merge feature
本地与远程分支关系
深入理解本地分支和远程分支的映射关系和同步机制。
远程分支概念
远程分支(remote branch)是远程仓库状态的本地引用,存储在refs/remotes/目录下。
远程分支特点
# - 不能直接修改
# - 通过fetch/push更新
# 2. 缓存远程状态
# - 反映上次fetch时的远程状态
# - 不是实时同步的
# 3. 独立于本地分支
# - origin/main ≠ main
# - 它们是独立的commit引用
远程分支存储结构:
.git/refs/remotes/
└── origin/
├── main (远程main分支)
├── develop (远程develop分支)
├── feature/login (远程feature/login分支)
└── HEAD (默认分支引用)
查看远程分支:
git branch -r
git show-ref refs/remotes/origin/main
本地分支与远程分支的映射
本地分支和远程分支通过"上游分支"(upstream)概念建立关联。
上游分支设置
git push -u origin main
# 等同于:
git push origin main
git branch --set-upstream-to=origin/main main
# 查看上游分支
git branch -vv
# 修改上游分支
git branch --set-upstream-to=origin/develop main
本地分支配置(.git/config):
[branch "main"]
remote = origin
merge = refs/heads/main
这表示:
- 本地main分支跟踪origin远程仓库
- 合并目标是origin/main分支
查看配置:
git config branch.main.remote
git config branch.main.merge
分支关系示意图
远程仓库 (GitHub):
main: A ← B ← C
本地仓库:
本地main分支
↓ (上游)
origin/main (远程引用)
↓ (fetch后)
main: A ← B ← C
工作流程:
1. 初始状态 (克隆后):
main (本地) = origin/main = 远程main
2. 本地提交后:
远程main: A ← B ← C
origin/main: A ← B ← C
main (本地): A ← B ← C ← D ← E
3. push后:
远程main: A ← B ← C ← D ← E
origin/main: A ← B ← C ← D ← E
main (本地): A ← B ← C ← D ← E
4. 远程有新提交后:
远程main: A ← B ← C ← D ← E ← F
origin/main: A ← B ← C ← D ← E (未fetch)
main (本地): A ← B ← C ← D ← E
5. fetch后:
远程main: A ← B ← C ← D ← E ← F
origin/main: A ← B ← C ← D ← E ← F
main (本地): A ← B ← C ← D ← E
6. merge后:
远程main: A ← B ← C ← D ← E ← F
origin/main: A ← B ← C ← D ← E ← F
main (本地): A ← B ← C ← D ← E ← F
fetch 更新机制
git fetch从远程仓库获取最新数据,更新远程分支引用。
fetch 执行过程
# 2. 获取远程的引用列表
# 3. 下载缺失的对象(commits, trees, blobs)
# 4. 更新 refs/remotes/* 引用
# 5. 不修改工作区和本地分支
# 注意:
# - 不自动合并到本地分支
# - 只更新远程引用
# - 可以安全地随时执行
fetch 命令示例
git fetch
# 获取特定远程仓库
git fetch origin
# 获取特定分支
git fetch origin main
# 获取所有远程仓库
git fetch --all
# 获取并修剪已删除的远程分支
git fetch --prune
push 同步机制
git push将本地提交推送到远程仓库,更新远程分支。
push 执行过程
# 2. 发送到远程仓库
# 3. 远程验证(权限、快进检查)
# 4. 更新远程引用
# 5. 触发远程hooks(如果配置)
# 快进检查:
# - 本地分支必须是远程分支的后继
# - 否则需要强制推送或先pull
push 命令示例
git push
# 推送指定分支
git push origin main
# 推送并设置上游
git push -u origin feature
# 推送所有分支
git push --all
# 推送标签
git push --tags
# 强制推送(谨慎使用)
git push --force
git push --force-with-lease
pull 合并机制
git pull是git fetch和git merge的组合。
pull 执行过程
# 1. git fetch origin
# - 更新 refs/remotes/origin/*
# 2. git merge origin/main
# - 合并远程更新到当前分支
# git pull --rebase = git fetch + git rebase
# - 先fetch,然后rebase当前分支
pull merge vs pull rebase:
场景: 本地和远程都有新提交
pull merge (默认):
远程main: A ← B ← C
本地main: A ← B ← D
pull后: A ← B ← C ← E ← D
\_____/
(merge commit)
pull rebase:
远程main: A ← B ← C
本地main: A ← B ← D
pull后: A ← B ← C ← D'
(rebased)
分支追踪关系查看
git branch -vv
# 输出示例:
# * main abc123 [origin/main: ahead 2, behind 1] Add feature
# develop def456 [origin/develop] Update code
# feature ghi789 [origin/feature: gone] Old feature
# 含义:
# - ahead 2: 本地领先远程2个提交
# - behind 1: 本地落后远程1个提交
# - gone: 远程分支已被删除
# 查看远程分支详情
git remote show origin
# 查看本地和远程的差异
git log HEAD..origin/main
远程分支删除与清理
git push origin --delete feature
# 或
git push origin :feature
# 删除本地已删除的远程分支引用
git fetch --prune
# 或
git remote prune origin
# 查看已删除的远程分支引用
git branch -r --merged
git branch -r --no-merged
多远程仓库管理
一个本地仓库可以关联多个远程仓库。
git remote add origin https://github.com/user/repo.git
git remote add upstream https://github.com/original/repo.git
git remote add fork https://github.com/fork/repo.git
# 查看所有远程仓库
git remote -v
# 从不同远程拉取
git fetch origin
git fetch upstream
# 推送到不同远程
git push origin main
git push fork feature
# 删除远程仓库
git remote remove fork
多远程仓库配置(.git/config):
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "upstream"]
url = https://github.com/original/repo.git
fetch = +refs/heads/*:refs/remotes/upstream/*
常用场景: Fork项目
- origin: 自己的fork仓库
- upstream: 原始项目仓库
- 定期从upstream获取更新,推送到origin
远程分支同步最佳实践
git fetch --all --prune
# 2. 查看状态再操作
git status
git branch -vv
# 3. 使用rebase保持历史整洁
git pull --rebase
# 4. 推送前先拉取
git pull --rebase origin main
git push origin main
# 5. 谨慎使用强制推送
git push --force-with-lease # 比较安全
# 而不是
git push --force # 危险
git fetch --prune定期清理已删除的远程分支引用。