Appearance
Git 核心概念
1. 知识点大纲
1.1 概述
在深入学习 Git 之前,理解其核心概念至关重要。Git 的设计理念与传统的版本控制系统有本质区别,掌握这些核心概念将帮助你更好地理解 Git 的工作方式,从而更高效地使用它。
Git 的核心概念可以类比为管理一个不断演进的故事:
- 仓库就是整本故事书
- 提交是故事的每一章
- 分支是故事的不同发展路线
- 标签是重要的章节标记
- 远程仓库是故事的备份副本
这些概念相互关联,共同构成了 Git 强大的版本控制能力。理解这些概念后,你会发现 Git 的各种操作都变得顺理成章。
为什么需要深入理解核心概念?
- 避免盲目执行命令,知其然更知其所以然
- 遇到问题时能够快速定位和解决
- 更好地规划项目开发流程
- 提高团队协作效率
1.2 基本概念
语法
仓库相关命令:
bash
# 初始化仓库
git init [directory]
# 克隆远程仓库
git clone <repository-url> [directory]
# 查看仓库状态
git status [options]
# 查看提交历史
git log [options] [<revision-range>]分支相关命令:
bash
# 列出分支
git branch [options]
# 创建分支
git branch <branch-name> [<start-point>]
# 删除分支
git branch -d <branch-name>
# 切换分支
git checkout <branch-name>
git switch <branch-name> # Git 2.23+ 推荐
# 创建并切换分支
git checkout -b <branch-name> [<start-point>]
git switch -c <branch-name> # Git 2.23+ 推荐远程仓库相关命令:
bash
# 查看远程仓库
git remote [-v]
# 添加远程仓库
git remote add <name> <url>
# 删除远程仓库
git remote remove <name>
# 重命名远程仓库
git remote rename <old-name> <new-name>标签相关命令:
bash
# 列出标签
git tag [-l <pattern>]
# 创建轻量标签
git tag <tag-name>
# 创建附注标签
git tag -a <tag-name> -m <message>
# 删除标签
git tag -d <tag-name>
# 推送标签到远程
git push origin <tag-name>
git push origin --tags语义
仓库(Repository)
仓库是 Git 管理的基本单位,包含:
- 工作目录:项目的实际文件
- .git 目录:Git 的元数据和对象数据库
- 配置文件:仓库级别的设置
bash
# 仓库结构示例
my-project/
├── .git/ # Git 仓库数据
│ ├── HEAD # 当前分支引用
│ ├── config # 仓库配置
│ ├── objects/ # 对象数据库
│ ├── refs/ # 引用(分支、标签)
│ └── hooks/ # 钩子脚本
├── .gitignore # 忽略文件配置
├── src/ # 项目源代码
└── README.md # 项目说明提交(Commit)
提交是 Git 中最重要的概念之一,它代表项目在某一时刻的完整快照。
每个提交包含:
- 树对象:指向项目目录结构
- 父提交:指向上一个提交(合并提交有多个父提交)
- 作者信息:谁创建了这次提交
- 提交者信息:谁最后应用了这次提交
- 提交信息:描述这次提交的内容
- 时间戳:提交时间
bash
# 提交结构示意
Commit Object
├── tree: a1b2c3d... # 指向根目录树对象
├── parent: x1y2z3... # 父提交(第一个提交没有 parent)
├── author: Zhang San <zhang@example.com>
├── authorDate: 2024-01-15 10:00:00
├── committer: Li Si <li@example.com>
├── commitDate: 2024-01-15 11:00:00
└── message: "feat: 添加用户登录功能"分支(Branch)
分支在 Git 中是一个非常轻量级的概念。本质上,分支只是一个指向某个提交的可移动指针。
bash
# 分支的本质
main ────► commit C
▲
│
feature ──────┘
# 分支文件内容
$ cat .git/refs/heads/main
abc1234567890abcdef1234567890abcdef12345678标签(Tag)
标签是给特定提交起的别名,通常用于标记发布版本。
两种标签类型:
- 轻量标签(Lightweight):只是一个指向提交的指针
- 附注标签(Annotated):包含完整信息的对象
bash
# 轻量标签
$ git tag v1.0.0
# 附注标签(推荐)
$ git tag -a v1.0.0 -m "Release version 1.0.0"
# 查看标签信息
$ git show v1.0.0
tag v1.0.0
Tagger: Zhang San <zhang@example.com>
Date: Mon Jan 15 10:00:00 2024 +0800
Release version 1.0.0
commit abc1234567890abcdef...
Author: Zhang San <zhang@example.com>
Date: Mon Jan 15 09:00:00 2024 +0800
feat: 完成所有功能开发远程仓库(Remote)
远程仓库是托管在网络上的仓库副本,用于团队协作和代码备份。
bash
# 远程仓库配置
$ git remote -v
origin https://github.com/user/repo.git (fetch)
origin https://github.com/user/repo.git (push)
upstream https://github.com/original/repo.git (fetch)
upstream https://github.com/original/repo.git (push)
# 远程分支
origin/main
origin/develop
upstream/mainHEAD 指针
HEAD 是一个特殊指针,指向当前所在的本地分支。
bash
# HEAD 指向分支
$ cat .git/HEAD
ref: refs/heads/main
# 分离 HEAD 状态(直接指向提交)
$ git checkout abc1234
$ cat .git/HEAD
abc1234567890abcdef1234567890abcdef12345678规范
分支命名规范:
bash
# 功能分支
feature/user-authentication
feature/payment-integration
# 修复分支
bugfix/login-validation
fix/session-timeout
# 紧急修复分支
hotfix/security-patch
hotfix/critical-bug
# 发布分支
release/v1.0.0
release/2024.01
# 开发分支
develop
dev
# 主分支
main
master标签命名规范:
遵循语义化版本(Semantic Versioning):
bash
# 格式:MAJOR.MINOR.PATCH
v1.0.0 # 首次正式发布
v1.0.1 # 修复 bug(PATCH)
v1.1.0 # 新增功能(MINOR)
v2.0.0 # 重大变更(MAJOR)
# 预发布版本
v1.0.0-alpha.1
v1.0.0-beta.1
v1.0.0-rc.1远程仓库名称规范:
bash
origin # 默认远程仓库名称(自己的仓库)
upstream # 上游仓库(fork 的源仓库)
backup # 备份仓库
deploy # 部署仓库1.3 原理深度解析
Git 对象模型
Git 是一个内容寻址文件系统,其核心是一个简单的键值对数据存储。
四种对象类型:
┌─────────────────────────────────────────────────────────────┐
│ Git 对象模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Commit 对象 │ │
│ │ │ │
│ │ - tree │──────► ┌─────────────┐ │
│ │ - parent │ │ Tree 对象 │ │
│ │ - author │ │ │ │
│ │ - message │ │ - entries │──┬──► ┌──────────┐ │
│ └─────────────┘ └─────────────┘ │ │ Blob 对象 │ │
│ │ │ │ │
│ │ │ 文件内容 │ │
│ │ └──────────┘ │
│ │ │
│ └──► ┌──────────┐ │
│ │ Tree 对象 │ │
│ │ (子目录) │ │
│ └──────────┘ │
│ │
│ ┌─────────────┐ │
│ │ Tag 对象 │ │
│ │ │ │
│ │ - object │──► Commit │
│ │ - type │ │
│ │ - tag name │ │
│ │ - tagger │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1. Blob(二进制大对象)
存储文件内容,不包含文件名和权限信息。
bash
# 创建 blob 对象
$ echo "Hello, Git" | git hash-object -w --stdin
8d01495a85a9f6e2f8e9bc4b5a5c5a5a5a5a5a5a
# 查看对象内容
$ git cat-file -p 8d01495a
Hello, Git
# 查看对象类型
$ git cat-file -t 8d01495a
blob2. Tree(树对象)
存储目录结构,包含文件名、权限和指向 blob 或其他 tree 的引用。
bash
# 查看树对象
$ git cat-file -p main^{tree}
100644 blob 8d01495a... README.md
040000 tree 4b825dc6... src
100644 blob e69de29b... .gitignore
# 权限说明
100644 # 普通文件
100755 # 可执行文件
040000 # 目录
120000 # 符号链接3. Commit(提交对象)
存储提交信息,指向一个树对象和父提交。
bash
# 查看提交对象
$ git cat-file -p HEAD
tree 4b825dc6dcb235d07a23c6a48a8a8a6a6a6a6a6a
parent abc1234567890abcdef1234567890abcdef12345678
author Zhang San <zhang@example.com> 1705287600 +0800
committer Zhang San <zhang@example.com> 1705287600 +0800
feat: 添加用户登录功能4. Tag(标签对象)
存储标签信息,指向一个提交对象。
bash
# 查看标签对象
$ git cat-file -p v1.0.0
object abc1234567890abcdef1234567890abcdef12345678
type commit
tag v1.0.0
tagger Zhang San <zhang@example.com> 1705287600 +0800
Release version 1.0.0Git 引用
引用是指向 Git 对象的指针,存储在 .git/refs 目录下。
引用类型:
.git/
├── HEAD # 当前分支引用
├── refs/
│ ├── heads/ # 本地分支
│ │ ├── main
│ │ └── develop
│ ├── remotes/ # 远程分支
│ │ └── origin/
│ │ ├── main
│ │ └── develop
│ └── tags/ # 标签
│ ├── v1.0.0
│ └── v1.1.0
└── packed-refs # 打包的引用(优化性能)引用规范:
bash
# 完整引用路径
refs/heads/main # 本地 main 分支
refs/remotes/origin/main # 远程 origin/main 分支
refs/tags/v1.0.0 # v1.0.0 标签
# 简写形式
main # 等同于 refs/heads/main
origin/main # 等同于 refs/remotes/origin/main
v1.0.0 # 等同于 refs/tags/v1.0.0Git 索引(暂存区)
Git 索引是一个二进制文件,存储在 .git/index,记录了工作目录和仓库之间的状态。
索引的作用:
- 作为工作目录和仓库之间的缓冲区
- 记录文件的元数据(修改时间、大小等)
- 支持部分暂存(git add -p)
bash
# 查看索引内容
$ git ls-files --stage
100644 8d01495a85a9f6e2f8e9bc4b5a5c5a5a5a5a5a5a 0 README.md
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 .gitignore
# 查看索引状态
$ git status
On branch main
Changes to be committed:
modified: README.md
Changes not staged for commit:
modified: config.phpGit 配置系统
Git 使用三级配置系统,优先级从高到低:
┌─────────────────────────────────────────────────────────────┐
│ Git 配置优先级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 本地配置(--local) │ │
│ │ 文件:.git/config │ │
│ │ 优先级:最高 │ │
│ │ 作用域:当前仓库 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 全局配置(--global) │ │
│ │ 文件:~/.gitconfig │ │
│ │ 优先级:中等 │ │
│ │ 作用域:当前用户所有仓库 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 系统配置(--system) │ │
│ │ 文件:/etc/gitconfig │ │
│ │ 优先级:最低 │ │
│ │ 作用域:系统所有用户 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘常用配置:
bash
# 用户信息
$ git config --global user.name "Zhang San"
$ git config --global user.email "zhang@example.com"
# 默认分支名称
$ git config --global init.defaultBranch main
# 编辑器
$ git config --global core.editor "code --wait"
# 别名
$ 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.lg "log --oneline --graph --all"
# 查看所有配置
$ git config --list --show-origin1.4 常见错误与踩坑点
错误 1:混淆本地分支和远程分支
错误表现:
bash
$ git branch -a
* main
remotes/origin/main
remotes/origin/develop
# 误以为可以直接在远程分支上工作
$ git checkout origin/develop
# 进入分离 HEAD 状态产生原因:
- 不理解本地分支和远程分支的区别
- 远程分支是只读的,不能直接修改
解决方案:
bash
# 正确做法:创建本地分支跟踪远程分支
$ git checkout -b develop origin/develop
# 或
$ git switch -c develop origin/develop
# 理解分支关系
$ git branch -vv
* main abc1234 [origin/main] feat: 添加功能
develop def5678 [origin/develop] feat: 开发中错误 2:不理解 FETCH_HEAD 和 ORIG_HEAD
错误表现:
bash
$ git pull
# 出现冲突,不知道如何回退
$ git reset --hard HEAD
# 发现之前的提交丢失了产生原因:
- 不了解 Git 的特殊引用
- 不知道如何恢复操作前的状态
解决方案:
bash
# FETCH_HEAD:记录最后一次 fetch 的分支
$ cat .git/FETCH_HEAD
abc1234567890abcdef1234567890abcdef12345678 branch 'main' of https://github.com/user/repo
# ORIG_HEAD:记录危险操作前的 HEAD
$ git merge feature
# ORIG_HEAD 指向合并前的提交
# 回退到操作前的状态
$ git reset --hard ORIG_HEAD
# 查看所有特殊引用
$ git show-ref | grep HEAD
abc1234 refs/heads/main
def5678 refs/remotes/origin/main错误 3:错误使用轻量标签和附注标签
错误表现:
bash
# 创建了轻量标签,但没有包含发布信息
$ git tag v1.0.0
# 推送标签后,团队成员看不到发布说明
$ git push origin v1.0.0产生原因:
- 不理解两种标签的区别
- 没有遵循最佳实践
解决方案:
bash
# 发布版本应使用附注标签
$ git tag -a v1.0.0 -m "Release version 1.0.0
新功能:
- 用户登录
- 权限管理
修复:
- 修复登录验证 bug
变更:
- 重构数据库连接模块"
# 查看标签详情
$ git show v1.0.0
# 轻量标签仅用于临时标记
$ git tag temp-marker错误 4:不理解 upstream 分支
错误表现:
bash
$ git push
fatal: The current branch feature has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature产生原因:
- 新创建的分支没有设置上游分支
- 不理解分支跟踪关系
解决方案:
bash
# 方法 1:推送时设置上游分支
$ git push -u origin feature
# 或
$ git push --set-upstream origin feature
# 方法 2:创建分支时设置跟踪
$ git checkout -b feature origin/feature
# 方法 3:为已存在的分支设置跟踪
$ git branch -u origin/feature feature
# 查看跟踪关系
$ git branch -vv
* feature abc1234 [origin/feature] feat: 新功能
main def5678 [origin/main] feat: 主分支错误 5:滥用 git reset
错误表现:
bash
$ git reset --hard HEAD~5
# 丢失了 5 个提交,无法恢复产生原因:
- 不理解 reset 的三种模式
- 没有先查看将要重置的内容
解决方案:
bash
# reset 的三种模式
# 1. --soft:只移动 HEAD,保留修改在暂存区
$ git reset --soft HEAD~1
# 适合:想修改提交信息或合并多个提交
# 2. --mixed(默认):移动 HEAD,保留修改在工作目录
$ git reset HEAD~1
# 适合:想重新组织提交
# 3. --hard:移动 HEAD,丢弃所有修改(危险)
$ git reset --hard HEAD~1
# 适合:确认要放弃修改
# 安全做法:先查看将要重置的内容
$ git log --oneline -5
$ git diff HEAD~5
# 使用 reflog 恢复误操作
$ git reflog
$ git reset --hard HEAD@{1}1.5 常见应用场景
场景 1:创建和管理分支
场景描述: 开发新功能时需要创建分支,完成后合并到主分支。
使用方法:
- 从主分支创建功能分支
- 在功能分支上开发
- 定期同步主分支更新
- 完成后合并回主分支
示例代码:
bash
# 确保主分支是最新的
$ git checkout main
$ git pull origin main
# 创建功能分支
$ git checkout -b feature/user-profile
Switched to a new branch 'feature/user-profile'
# 开发功能
$ echo "用户资料功能" > profile.php
$ git add profile.php
$ git commit -m "feat(profile): 添加用户资料基础功能"
# 继续开发
$ echo "头像上传" >> profile.php
$ git add profile.php
$ git commit -m "feat(profile): 添加头像上传功能"
# 同步主分支更新
$ git checkout main
$ git pull origin main
$ git checkout feature/user-profile
$ git merge main
# 或使用 rebase
$ git rebase main
# 推送功能分支
$ git push -u origin feature/user-profile
# 合并到主分支(通过 Pull Request 或本地合并)
$ git checkout main
$ git merge feature/user-profile
$ git push origin main
# 删除功能分支
$ git branch -d feature/user-profile
$ git push origin --delete feature/user-profile运行结果分析:
- 分支隔离开发,不影响主分支
- 定期同步避免大量冲突
- 合并后清理分支保持仓库整洁
场景 2:使用标签管理版本发布
场景描述: 项目开发完成,需要发布正式版本并打标签。
使用方法:
- 确保代码处于可发布状态
- 创建附注标签
- 推送标签到远程
- 后续维护新版本
示例代码:
bash
# 确保在正确的分支
$ git checkout main
Already on 'main'
# 确保代码是最新的
$ git pull origin main
# 查看最近的提交
$ git log --oneline -5
abc1234 fix: 修复最后的 bug
def5678 feat: 完成所有功能
ghi9012 docs: 更新文档
# 创建附注标签
$ git tag -a v1.0.0 -m "Release v1.0.0
新功能:
- 用户注册登录
- 个人资料管理
- 权限系统
改进:
- 优化数据库查询性能
- 改进用户界面
修复:
- 修复登录验证问题
- 修复文件上传 bug"
# 查看标签
$ git tag -l
v1.0.0
# 查看标签详情
$ git show v1.0.0
tag v1.0.0
Tagger: Zhang San <zhang@example.com>
Date: Mon Jan 15 10:00:00 2024 +0800
Release v1.0.0
...
# 推送标签到远程
$ git push origin v1.0.0
To https://github.com/user/repo.git
* [new tag] v1.0.0 -> v1.0.0
# 推送所有标签
$ git push origin --tags
# 后续开发新版本
$ git checkout -b develop v1.0.0
# 或在 main 分支继续开发
# 发布补丁版本
$ git tag -a v1.0.1 -m "Hotfix: 修复紧急 bug"
$ git push origin v1.0.1运行结果分析:
- 附注标签包含完整的发布信息
- 标签便于追溯历史版本
- 可以随时检出特定版本
场景 3:管理多个远程仓库
场景描述: 项目需要同时推送到多个远程仓库(如 GitHub 和 GitLab)。
使用方法:
- 添加多个远程仓库
- 分别推送和拉取
- 设置默认推送仓库
示例代码:
bash
# 查看当前远程仓库
$ git remote -v
origin https://github.com/user/repo.git (fetch)
origin https://github.com/user/repo.git (push)
# 添加第二个远程仓库
$ git remote add gitlab https://gitlab.com/user/repo.git
# 添加第三个远程仓库(只读)
$ git remote add upstream https://github.com/original/repo.git
$ git remote set-url --push upstream no-push
# 查看所有远程仓库
$ git remote -v
origin https://github.com/user/repo.git (fetch)
origin https://github.com/user/repo.git (push)
gitlab https://gitlab.com/user/repo.git (fetch)
gitlab https://gitlab.com/user/repo.git (push)
upstream https://github.com/original/repo.git (fetch)
upstream no-push (push)
# 推送到指定远程仓库
$ git push origin main
$ git push gitlab main
# 从 upstream 拉取更新
$ git fetch upstream
$ git merge upstream/main
# 同时推送到多个仓库
$ git remote set-url --add --push origin https://github.com/user/repo.git
$ git remote set-url --add --push origin https://gitlab.com/user/repo.git
$ git push origin main
# 同时推送到 GitHub 和 GitLab运行结果分析:
- 多远程仓库实现代码备份
- upstream 用于跟踪原始仓库
- 可以灵活控制推送和拉取
场景 4:使用 Git 别名提高效率
场景描述: 频繁使用的命令太长,需要创建简短的别名。
使用方法:
- 配置常用别名
- 使用别名执行命令
- 创建复杂命令别名
示例代码:
bash
# 基本别名
$ 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 st
On branch main
nothing to commit, working tree clean
# 复杂命令别名
$ git config --global alias.lg "log --oneline --graph --all"
$ git lg
* abc1234 (HEAD -> main, origin/main) feat: 最新提交
* def5678 feat: 上一个提交
| * ghi9012 (feature) feat: 功能分支
|/
* xyz5678 feat: 共同祖先
# 查看最后提交
$ git config --global alias.last 'log -1 HEAD'
$ git last
commit abc1234567890abcdef...
Author: Zhang San <zhang@example.com>
Date: Mon Jan 15 10:00:00 2024 +0800
feat: 最新提交
# 撤销最后一次提交
$ git config --global alias.undo 'reset --soft HEAD~1'
# 查看所有别名
$ git config --global --get-regexp alias
alias.st status
alias.co checkout
alias.br branch
alias.ci commit
alias.lg log --oneline --graph --all
alias.last log -1 HEAD
alias.undo reset --soft HEAD~1运行结果分析:
- 别名大幅提高工作效率
- 复杂命令可以简化为简短别名
- 配置存储在全局配置文件中
场景 5:使用 .gitignore 管理忽略文件
场景描述: 项目中有不需要版本控制的文件,需要配置忽略规则。
使用方法:
- 创建 .gitignore 文件
- 配置忽略规则
- 清理已跟踪的文件
示例代码:
bash
# 创建 .gitignore 文件
$ cat > .gitignore << 'EOF'
# 操作系统文件
.DS_Store
Thumbs.db
# IDE 配置
.idea/
.vscode/
*.swp
*.swo
# 依赖目录
/vendor/
/node_modules/
# 编译输出
/build/
/dist/
*.o
*.so
# 日志文件
*.log
/logs/
# 环境配置
.env
.env.local
.env.*.local
# 临时文件
*.tmp
*.cache
*.bak
# 敏感信息
*.key
*.pem
config/production.php
EOF
# 添加 .gitignore
$ git add .gitignore
$ git commit -m "chore: 添加 .gitignore 配置"
# 清理已跟踪但应该忽略的文件
$ git rm --cached .env
$ git rm --cached -r .idea/
$ git commit -m "chore: 从版本控制中移除敏感文件"
# 查看哪些文件被忽略
$ git status --ignored
On branch main
Ignored files:
.env
.idea/
vendor/
# 检查某个文件为什么被忽略
$ git check-ignore -v .env
.gitignore:18:.env .env运行结果分析:
- .gitignore 避免提交不必要的文件
- 使用
git rm --cached清理已跟踪的文件 - 可以使用模板快速创建配置
1.6 企业级进阶应用
Git 子模块管理
子模块允许将一个 Git 仓库作为另一个 Git 仓库的子目录。
bash
# 添加子模块
$ git submodule add https://github.com/user/library.git lib/library
# 初始化子模块
$ git submodule init
$ git submodule update
# 或一步完成
$ git submodule update --init --recursive
# 克隆包含子模块的项目
$ git clone --recursive https://github.com/user/project.git
# 更新子模块到最新版本
$ git submodule update --remote
# 子模块配置文件
$ cat .gitmodules
[submodule "lib/library"]
path = lib/library
url = https://github.com/user/library.gitGit 工作树(Worktree)
工作树允许同时检出多个分支到不同目录。
bash
# 创建新工作树
$ git worktree add ../project-feature feature
# 创建新分支的工作树
$ git worktree add -b hotfix ../project-hotfix main
# 列出所有工作树
$ git worktree list
/main/project abc1234 [main]
/main/project-feature def5678 [feature]
/main/project-hotfix ghi9012 [hotfix]
# 在工作树中工作
$ cd ../project-feature
$ git status
On branch feature
# 删除工作树
$ git worktree remove ../project-featureGit 钩子高级应用
pre-push 钩子:推送前运行测试
bash
#!/bin/bash
# .git/hooks/pre-push
protected_branch='main'
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
if [ $protected_branch = $current_branch ]; then
echo "正在运行测试..."
# 运行 PHP 测试
./vendor/bin/phpunit
if [ $? -ne 0 ]; then
echo "测试失败!禁止推送到 main 分支"
exit 1
fi
echo "测试通过!"
fi
exit 0post-receive 钩子:自动部署
bash
#!/bin/bash
# 服务器端钩子:/path/to/repo.git/hooks/post-receive
while read oldrev newrev ref
do
branch=$(echo $ref | cut -d/ -f3)
if [ "main" = "$branch" ]; then
echo "部署生产环境..."
GIT_WORK_TREE=/var/www/production git checkout -f main
cd /var/www/production
composer install --no-dev
php artisan migrate --force
php artisan config:cache
echo "部署完成!"
fi
if [ "develop" = "$branch" ]; then
echo "部署开发环境..."
GIT_WORK_TREE=/var/www/staging git checkout -f develop
cd /var/www/staging
composer install
echo "部署完成!"
fi
done1.7 行业最佳实践
实践 1:使用分支策略
实践内容: 根据团队规模和项目特点选择合适的分支策略。
推荐理由:
- 规范开发流程
- 减少冲突
- 保证代码质量
常见分支策略:
| 策略 | 适用场景 | 分支数量 |
|---|---|---|
| GitHub Flow | 持续部署项目 | 少 |
| Git Flow | 有计划发布周期 | 多 |
| GitLab Flow | 介于两者之间 | 中 |
| Trunk Based | 大型团队 | 少 |
实践 2:保护重要分支
实践内容: 在远程仓库设置分支保护规则,禁止直接推送。
推荐理由:
- 强制代码审查
- 防止意外修改
- 保证主分支稳定
bash
# GitHub 分支保护规则
# Settings -> Branches -> Add rule
# 保护规则示例:
# - Require pull request reviews before merging
# - Require status checks to pass before merging
# - Require signed commits
# - Include administrators实践 3:使用语义化版本
实践内容: 遵循语义化版本规范命名标签。
推荐理由:
- 清晰传达版本变更
- 便于依赖管理
- 自动化工具支持
bash
# 语义化版本格式
MAJOR.MINOR.PATCH
# 示例
v1.0.0 -> v1.0.1 # PATCH: 修复 bug
v1.0.1 -> v1.1.0 # MINOR: 新功能,向后兼容
v1.1.0 -> v2.0.0 # MAJOR: 重大变更,不兼容
# 预发布版本
v2.0.0-alpha.1
v2.0.0-beta.1
v2.0.0-rc.1实践 4:编写清晰的提交信息
实践内容: 使用约定式提交规范,编写结构化的提交信息。
推荐理由:
- 自动生成变更日志
- 便于理解项目历史
- 支持自动化工具
bash
# 约定式提交格式
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
# 示例
feat(auth): 添加 OAuth2 登录支持
- 支持 Google 登录
- 支持 GitHub 登录
- 添加登录回调处理
Closes #123
Breaking Change: 登录接口参数变更实践 5:定期清理分支
实践内容: 定期删除已合并的功能分支和过时的远程分支。
推荐理由:
- 保持仓库整洁
- 减少混淆
- 提高性能
bash
# 删除已合并的本地分支
$ git branch --merged main | grep -v "^\*\|main\|develop" | xargs git branch -d
# 删除远程已删除的分支引用
$ git fetch --prune
# 或
$ git remote prune origin
# 查看远程分支状态
$ git remote show origin1.8 常见问题答疑(FAQ)
问题 1:如何查看某个文件的历史版本?
问题描述: 需要查看或恢复某个文件在特定版本的内容。
回答内容:
Git 提供了多种方式查看文件历史:
bash
# 查看文件的所有历史版本
$ git log --oneline --follow config.php
abc1234 feat: 更新配置
def5678 feat: 添加配置
# 查看特定版本的文件内容
$ git show def5678:config.php
<?php
return [
'debug' => true,
];
# 恢复文件到特定版本
$ git checkout def5678 -- config.php
# 或
$ git restore --source=def5678 config.php
# 查看文件的修改历史(每次修改的差异)
$ git log -p config.php
# 查看谁修改了文件的每一行
$ git blame config.php
abc1234 (Zhang San 2024-01-15 10:00:00 1) <?php
def5678 (Li Si 2024-01-14 15:00:00 2) return [
ghi9012 (Wang Wu 2024-01-13 09:00:00 3) 'debug' => true,问题 2:如何比较两个分支的差异?
问题描述: 需要了解两个分支之间有哪些不同。
回答内容:
bash
# 查看分支差异概览
$ git diff main..feature
# 显示 feature 相对于 main 的所有差异
# 只看文件名
$ git diff --name-only main..feature
src/Login.php
src/User.php
# 查看文件统计
$ git diff --stat main..feature
src/Login.php | 10 ++++++++++
src/User.php | 5 +++++
2 files changed, 15 insertions(+)
# 查看提交差异
$ git log main..feature --oneline
ghi9012 feat: 添加用户功能
def5678 feat: 添加登录功能
# 查看两个分支的共同祖先
$ git merge-base main feature
xyz5678
# 比较分支与共同祖先的差异
$ git diff $(git merge-base main feature)..feature问题 3:如何撤销已推送的提交?
问题描述: 提交已推送到远程,发现有问题需要撤销。
回答内容:
对于已推送的提交,应使用 git revert 而不是 git reset:
bash
# 撤销最后一次提交(创建反向提交)
$ git revert HEAD
# 会打开编辑器,确认后创建新的撤销提交
# 撤销特定提交
$ git revert abc1234
# 撤销多个提交
$ git revert abc1234..def5678
# 撤销但不自动提交(可以合并多个撤销)
$ git revert -n abc1234
$ git revert -n def5678
$ git commit -m "revert: 撤销最近的两个提交"
# 推送撤销提交
$ git push origin main为什么不用 reset?
reset会改写历史,已推送的提交可能被其他人拉取- 改写已推送的历史会导致其他开发者的仓库出现问题
revert创建新提交,不会影响历史
问题 4:如何处理 "detached HEAD" 状态?
问题描述: 检出某个提交或标签后进入分离 HEAD 状态。
回答内容:
分离 HEAD 状态意味着 HEAD 直接指向某个提交,而不是分支:
bash
# 进入分离 HEAD 状态
$ git checkout v1.0.0
Note: switching to 'v1.0.0'.
You are in 'detached HEAD' state.
# 查看状态
$ git status
HEAD detached at v1.0.0
# 如果要在此状态工作,创建新分支
$ git checkout -b fix-v1.0.0
# 如果只是查看,返回之前的分支
$ git checkout main
# 或
$ git switch -
# 查看分离 HEAD 状态下的提交(可能丢失)
$ git reflog
abc1234 HEAD@{0}: checkout: moving from main to v1.0.0
def5678 HEAD@{1}: checkout: moving from feature to main
# 恢复分离状态下创建的提交
$ git branch save-my-work abc1234问题 5:如何合并多个提交为一个?
问题描述: 功能开发过程中创建了多个小提交,合并前想整理为一个提交。
回答内容:
使用交互式 rebase 合并提交:
bash
# 查看提交历史
$ git log --oneline -5
ghi9012 feat: 修复 typo
def5678 feat: 添加功能 B
abc1234 feat: 添加功能 A
xyz5678 feat: 初始化
# 交互式 rebase 最近 3 个提交
$ git rebase -i HEAD~3
# 编辑器中显示:
pick abc1234 feat: 添加功能 A
pick def5678 feat: 添加功能 B
pick ghi9012 feat: 修复 typo
# 修改为:
pick abc1234 feat: 添加功能 A
squash def5678 feat: 添加功能 B
squash ghi9012 feat: 修复 typo
# 保存后编辑合并后的提交信息
feat: 添加用户管理功能
- 添加功能 A
- 添加功能 B
- 修复 typo
# 结果
$ git log --oneline -3
jkl3456 feat: 添加用户管理功能
xyz5678 feat: 初始化操作类型:
pick:保留提交reword:修改提交信息squash:合并到前一个提交,保留信息fixup:合并到前一个提交,丢弃信息drop:删除提交
问题 6:如何同步 fork 的仓库?
问题描述: fork 了别人的仓库,需要同步原仓库的更新。
回答内容:
bash
# 查看当前远程仓库
$ git remote -v
origin https://github.com/your-username/repo.git (fetch)
origin https://github.com/your-username/repo.git (push)
# 添加上游仓库
$ git remote add upstream https://github.com/original-owner/repo.git
# 获取上游更新
$ git fetch upstream
remote: Enumerating objects: 100, done.
remote: Counting objects: 100% (100/100), done.
...
# 查看上游分支
$ git branch -r
origin/main
upstream/main
upstream/develop
# 合并上游更新到本地
$ git checkout main
$ git merge upstream/main
Updating abc1234..def5678
Fast-forward
README.md | 10 ++++++++++
1 file changed, 10 insertions(+)
# 推送到自己的 fork
$ git push origin main
# 保持 fork 的分支与上游同步
$ git checkout develop
$ git merge upstream/develop
$ git push origin develop1.9 实战练习
基础练习:创建和管理标签
练习目标: 掌握标签的创建、查看和删除操作。
解题思路:
- 创建项目并提交
- 创建轻量标签和附注标签
- 查看标签信息
- 删除标签
常见误区:
- 混淆轻量标签和附注标签
- 忘记推送标签到远程
分步提示:
bash
# 步骤 1:创建项目
$ mkdir tag-practice
$ cd tag-practice
$ git init
$ echo "# Tag Practice" > README.md
$ git add .
$ git commit -m "feat: 初始化项目"
# 步骤 2:创建轻量标签
# 提示:git tag <tag-name>
# 步骤 3:创建附注标签
# 提示:git tag -a <tag-name> -m "message"
# 步骤 4:查看所有标签
# 提示:git tag -l
# 步骤 5:查看标签详情
# 提示:git show <tag-name>
# 步骤 6:删除本地标签
# 提示:git tag -d <tag-name>参考代码:
bash
# 创建项目
$ mkdir tag-practice
$ cd tag-practice
$ git init
$ echo "# Tag Practice" > README.md
$ git add .
$ git commit -m "feat: 初始化项目"
[main (root-commit) abc1234] feat: 初始化项目
1 file changed, 1 insertion(+)
# 创建轻量标签
$ git tag v0.1.0
# 创建附注标签
$ git tag -a v1.0.0 -m "Release v1.0.0
首个正式版本发布"
# 查看所有标签
$ git tag -l
v0.1.0
v1.0.0
# 查看标签详情
$ git show v1.0.0
tag v1.0.0
Tagger: Zhang San <zhang@example.com>
Date: Mon Jan 15 10:00:00 2024 +0800
Release v1.0.0
首个正式版本发布
commit abc1234567890abcdef...
Author: Zhang San <zhang@example.com>
...
# 删除轻量标签
$ git tag -d v0.1.0
Deleted tag 'v0.1.0' (was abc1234)进阶练习:配置和使用 Git 别名
练习目标: 创建实用的 Git 别名,提高工作效率。
解题思路:
- 配置基本别名
- 配置复杂命令别名
- 测试别名功能
常见误区:
- 别名命令需要用引号包裹
- 复杂命令需要正确转义
分步提示:
bash
# 步骤 1:配置基本别名
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
# 步骤 2:配置日志别名
# 提示:创建一个美观的日志显示格式
# 步骤 3:配置撤销别名
# 提示:创建撤销最后一次提交的别名
# 步骤 4:测试所有别名
# 步骤 5:查看配置的别名参考代码:
bash
# 配置基本别名
$ 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.lg "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
# 配置撤销别名
$ git config --global alias.undo 'reset --soft HEAD~1'
$ git config --global alias.unstage 'reset HEAD --'
# 配置查看别名
$ git config --global alias.aliases 'config --get-regexp alias'
# 测试别名
$ git st
On branch main
nothing to commit, working tree clean
$ git lg
* abc1234 - (HEAD -> main) feat: 初始化项目 (2 hours ago) <Zhang San>
$ git aliases
alias.st status
alias.co checkout
alias.br branch
alias.ci commit
alias.lg log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
alias.undo reset --soft HEAD~1
alias.unstage reset HEAD --
alias.aliases config --get-regexp alias挑战练习:实现完整的分支工作流
练习目标: 模拟真实的团队协作场景,实现完整的分支工作流。
解题思路:
- 创建主分支初始提交
- 创建功能分支开发
- 创建发布分支准备发布
- 合并到主分支并打标签
- 创建热修复分支修复紧急问题
常见误区:
- 分支命名不规范
- 合并顺序错误
- 忘记打标签
分步提示:
bash
# 步骤 1:初始化项目
$ mkdir workflow-practice
$ cd workflow-practice
$ git init
$ echo "# Workflow Practice" > README.md
$ git add .
$ git commit -m "feat: 初始化项目"
# 步骤 2:创建 develop 分支
$ git checkout -b develop
# 步骤 3:创建功能分支并开发
$ git checkout -b feature/login
# 添加登录功能
# 提交
# 步骤 4:合并功能分支到 develop
$ git checkout develop
$ git merge feature/login
# 步骤 5:创建发布分支
$ git checkout -b release/v1.0.0
# 修复 bug、更新版本号
# 提交
# 步骤 6:合并到 main 和 develop
$ git checkout main
$ git merge release/v1.0.0
$ git tag -a v1.0.0 -m "Release v1.0.0"
$ git checkout develop
$ git merge release/v1.0.0
# 步骤 7:创建热修复分支
$ git checkout -b hotfix/critical-bug main
# 修复紧急 bug
# 提交
# 步骤 8:合并热修复到 main 和 develop
$ git checkout main
$ git merge hotfix/critical-bug
$ git tag -a v1.0.1 -m "Hotfix v1.0.1"
$ git checkout develop
$ git merge hotfix/critical-bug
# 步骤 9:清理分支
$ git branch -d feature/login
$ git branch -d release/v1.0.0
$ git branch -d hotfix/critical-bug
# 步骤 10:查看最终历史
$ git log --oneline --graph --all参考代码:
bash
# 初始化项目
$ mkdir workflow-practice
$ cd workflow-practice
$ git init
$ echo "# Workflow Practice" > README.md
$ echo "version: 1.0.0" > VERSION
$ git add .
$ git commit -m "feat: 初始化项目"
[main (root-commit) abc1234] feat: 初始化项目
2 files changed, 2 insertions(+)
# 创建 develop 分支
$ git checkout -b develop
Switched to a new branch 'develop'
# 创建功能分支
$ git checkout -b feature/login
Switched to a new branch 'feature/login'
# 开发登录功能
$ mkdir -p src/Controllers
$ cat > src/Controllers/LoginController.php << 'EOF'
<?php
class LoginController
{
public function login()
{
return 'Login successful';
}
}
EOF
$ git add .
$ git commit -m "feat(login): 添加登录控制器"
[feature/login def5678] feat(login): 添加登录控制器
1 file changed, 9 insertions(+)
# 合并到 develop
$ git checkout develop
Switched to branch 'develop'
$ git merge feature/login
Updating abc1234..def5678
Fast-forward
src/Controllers/LoginController.php | 9 +++++++++
1 file changed, 9 insertions(+)
# 创建发布分支
$ git checkout -b release/v1.0.0
Switched to a new branch 'release/v1.0.0'
# 更新版本号
$ echo "version: 1.0.0" > VERSION
$ git add VERSION
$ git commit -m "chore: 更新版本号到 1.0.0"
[release/v1.0.0 ghi9012] chore: 更新版本号到 1.0.0
1 file changed, 1 insertion(+), 1 deletion(-)
# 合并到 main
$ git checkout main
Switched to branch 'main'
$ git merge release/v1.0.0
Updating abc1234..ghi9012
Fast-forward
VERSION | 1 +
src/Controllers/LoginController.php | 9 +++++++++
2 files changed, 10 insertions(+)
# 打标签
$ git tag -a v1.0.0 -m "Release v1.0.0"
# 合并到 develop
$ git checkout develop
Switched to branch 'develop'
$ git merge release/v1.0.0
Merge made by the 'recursive' strategy.
VERSION | 1 +
1 file changed, 1 insertion(+)
# 创建热修复分支
$ git checkout -b hotfix/critical-bug main
Switched to a new branch 'hotfix/critical-bug'
# 修复 bug
$ cat > src/Controllers/LoginController.php << 'EOF'
<?php
class LoginController
{
public function login()
{
// 修复:添加输入验证
return 'Login successful';
}
}
EOF
$ git add .
$ git commit -m "fix(login): 修复登录验证漏洞"
[hotfix/critical-bug jkl3456] fix(login): 修复登录验证漏洞
1 file changed, 2 insertions(+)
# 更新版本号
$ echo "version: 1.0.1" > VERSION
$ git add VERSION
$ git commit -m "chore: 更新版本号到 1.0.1"
[hotfix/critical-bug mno7890] chore: 更新版本号到 1.0.1
1 file changed, 1 insertion(+), 1 deletion(-)
# 合并到 main
$ git checkout main
$ git merge hotfix/critical-bug
Updating ghi9012..mno7890
Fast-forward
VERSION | 2 +-
src/Controllers/LoginController.php | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
# 打标签
$ git tag -a v1.0.1 -m "Hotfix v1.0.1"
# 合并到 develop
$ git checkout develop
$ git merge hotfix/critical-bug
Merge made by the 'recursive' strategy.
VERSION | 2 +-
src/Controllers/LoginController.php | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
# 清理分支
$ git branch -d feature/login
Deleted branch feature/login (was def5678).
$ git branch -d release/v1.0.0
Deleted branch release/v1.0.0 (was ghi9012).
$ git branch -d hotfix/critical-bug
Deleted branch hotfix/critical-bug (was mno7890).
# 查看最终历史
$ git log --oneline --graph --all
* pqr1234 (HEAD -> develop) Merge branch 'hotfix/critical-bug' into develop
|\
| * mno7890 (tag: v1.0.1, main) chore: 更新版本号到 1.0.1
| * jkl3456 fix(login): 修复登录验证漏洞
* | stu5678 Merge branch 'release/v1.0.0' into develop
|\ \
| |/
|/|
| * ghi9012 (tag: v1.0.0) chore: 更新版本号到 1.0.0
|/
* def5678 feat(login): 添加登录控制器
* abc1234 feat: 初始化项目1.10 知识点总结
核心要点
仓库是 Git 的基本单位
- 包含工作目录和 .git 目录
- 可以是本地仓库或远程仓库
提交是项目的历史快照
- 包含完整的目录结构和元数据
- 通过 SHA-1 哈希值唯一标识
分支是轻量级指针
- 指向某个提交
- 创建和切换非常快速
标签是版本标记
- 附注标签包含完整信息
- 用于标记发布版本
远程仓库用于协作
- origin 是默认远程仓库名
- 可以配置多个远程仓库
易错点回顾
| 易错点 | 正确做法 |
|---|---|
| 混淆本地分支和远程分支 | 理解远程分支是只读的 |
| 不理解特殊引用 | 学习 FETCH_HEAD、ORIG_HEAD 等 |
| 错误使用标签类型 | 发布版本使用附注标签 |
| 新分支没有上游 | 推送时使用 -u 参数 |
| 滥用 git reset | 已推送的提交使用 revert |
1.11 拓展参考资料
官方文档
进阶学习路径
高级分支操作
- git cherry-pick
- git rebase -i
- git bisect
Git 内部原理
- 底层命令(plumbing)
- 对象存储格式
- 引用规范
团队协作工具
- GitHub/GitLab/Gitea
- Pull Request/Merge Request
- Code Review
推荐资源
学习建议:
- 多练习分支操作,理解分支的本质
- 尝试不同的工作流,找到适合团队的方案
- 阅读优秀开源项目的 Git 历史
