Skip to content

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/main

HEAD 指针

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
blob

2. 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.0

Git 引用

引用是指向 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.0

Git 索引(暂存区)

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.php

Git 配置系统

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-origin

1.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:创建和管理分支

场景描述: 开发新功能时需要创建分支,完成后合并到主分支。

使用方法:

  1. 从主分支创建功能分支
  2. 在功能分支上开发
  3. 定期同步主分支更新
  4. 完成后合并回主分支

示例代码:

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:使用标签管理版本发布

场景描述: 项目开发完成,需要发布正式版本并打标签。

使用方法:

  1. 确保代码处于可发布状态
  2. 创建附注标签
  3. 推送标签到远程
  4. 后续维护新版本

示例代码:

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)。

使用方法:

  1. 添加多个远程仓库
  2. 分别推送和拉取
  3. 设置默认推送仓库

示例代码:

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 别名提高效率

场景描述: 频繁使用的命令太长,需要创建简短的别名。

使用方法:

  1. 配置常用别名
  2. 使用别名执行命令
  3. 创建复杂命令别名

示例代码:

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 管理忽略文件

场景描述: 项目中有不需要版本控制的文件,需要配置忽略规则。

使用方法:

  1. 创建 .gitignore 文件
  2. 配置忽略规则
  3. 清理已跟踪的文件

示例代码:

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.git

Git 工作树(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-feature

Git 钩子高级应用

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 0

post-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
done

1.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 origin

1.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 develop

1.9 实战练习

基础练习:创建和管理标签

练习目标: 掌握标签的创建、查看和删除操作。

解题思路:

  1. 创建项目并提交
  2. 创建轻量标签和附注标签
  3. 查看标签信息
  4. 删除标签

常见误区:

  • 混淆轻量标签和附注标签
  • 忘记推送标签到远程

分步提示:

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 别名,提高工作效率。

解题思路:

  1. 配置基本别名
  2. 配置复杂命令别名
  3. 测试别名功能

常见误区:

  • 别名命令需要用引号包裹
  • 复杂命令需要正确转义

分步提示:

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

挑战练习:实现完整的分支工作流

练习目标: 模拟真实的团队协作场景,实现完整的分支工作流。

解题思路:

  1. 创建主分支初始提交
  2. 创建功能分支开发
  3. 创建发布分支准备发布
  4. 合并到主分支并打标签
  5. 创建热修复分支修复紧急问题

常见误区:

  • 分支命名不规范
  • 合并顺序错误
  • 忘记打标签

分步提示:

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 知识点总结

核心要点

  1. 仓库是 Git 的基本单位

    • 包含工作目录和 .git 目录
    • 可以是本地仓库或远程仓库
  2. 提交是项目的历史快照

    • 包含完整的目录结构和元数据
    • 通过 SHA-1 哈希值唯一标识
  3. 分支是轻量级指针

    • 指向某个提交
    • 创建和切换非常快速
  4. 标签是版本标记

    • 附注标签包含完整信息
    • 用于标记发布版本
  5. 远程仓库用于协作

    • origin 是默认远程仓库名
    • 可以配置多个远程仓库

易错点回顾

易错点正确做法
混淆本地分支和远程分支理解远程分支是只读的
不理解特殊引用学习 FETCH_HEAD、ORIG_HEAD 等
错误使用标签类型发布版本使用附注标签
新分支没有上游推送时使用 -u 参数
滥用 git reset已推送的提交使用 revert

1.11 拓展参考资料

官方文档

进阶学习路径

  1. 高级分支操作

    • git cherry-pick
    • git rebase -i
    • git bisect
  2. Git 内部原理

    • 底层命令(plumbing)
    • 对象存储格式
    • 引用规范
  3. 团队协作工具

    • GitHub/GitLab/Gitea
    • Pull Request/Merge Request
    • Code Review

推荐资源


学习建议:

  • 多练习分支操作,理解分支的本质
  • 尝试不同的工作流,找到适合团队的方案
  • 阅读优秀开源项目的 Git 历史

知识点承接:什么是 Git → 本教程 → Git 工作原理