Skip to content

什么是 Git

1. 知识点大纲

1.1 概述

Git 是目前世界上最先进的分布式版本控制系统(Distributed Version Control System,简称 DVCS)。它由 Linus Torvalds(Linux 之父)于 2005 年创建,最初是为了更好地管理 Linux 内核开发而设计。

在软件开发过程中,我们经常面临这样的问题:

  • 代码修改后发现问题,想回退到之前的版本
  • 多人协作开发,代码容易冲突
  • 需要同时维护多个版本(如开发版、稳定版)

Git 就像是一个"时光机器",它可以记录文件的每一次修改历史,让你随时可以回到过去某个时间点的状态。想象一下,你在写一篇重要的文档,每次修改前都复制一份备份,最终你会得到很多个版本:文档_v1.doc文档_v2.doc文档_最终版.doc文档_最终版_改.doc...Git 就是帮你自动管理这些版本的工具,而且比手动复制更智能、更高效。

为什么选择 Git?

  • 免费开源:任何人都可以免费使用和修改
  • 速度快:几乎所有操作都在本地完成,无需联网
  • 分布式:每个人都有完整的版本库,不依赖中央服务器
  • 强大分支:创建和合并分支非常快速和简单
  • 安全性高:使用 SHA-1 哈希算法确保数据完整性

1.2 基本概念

语法

Git 的基本命令语法格式:

bash
git <command> [<args>]

常用命令一览:

命令说明示例
git init初始化仓库git init
git clone克隆远程仓库git clone https://github.com/user/repo.git
git add添加文件到暂存区git add filename.txt
git commit提交更改git commit -m "提交说明"
git status查看状态git status
git log查看提交历史git log --oneline
git diff查看差异git diff
git branch分支管理git branch feature
git checkout切换分支git checkout feature
git merge合并分支git merge feature
git pull拉取远程更新git pull origin main
git push推送到远程git push origin main

语义

版本控制(Version Control)

版本控制是一种记录文件内容变化的方式,让你能够查阅特定版本的修订情况。它不仅适用于代码,也适用于任何类型的文件。

分布式 vs 集中式

集中式版本控制系统(如 SVN)有一个单一的集中管理的服务器,保存所有文件的修订版本。团队成员通过客户端连接到这台服务器,获取最新的文件或提交更新。

分布式版本控制系统(如 Git)中,客户端不只是提取最新版本的文件快照,而是把原始代码仓库完整地镜像下来。这样任何一处协同工作用的服务器发生故障,都可以用任何一个镜像出来的本地仓库恢复。

集中式架构:
┌─────────────┐
│   中央服务器  │
│  (唯一仓库)  │
└──────┬──────┘

  ┌────┴────┐
  │         │
┌─▼──┐   ┌──▼─┐
│客户端│   │客户端│
└────┘   └────┘

分布式架构:
┌────────┐     ┌────────┐
│ 远程仓库 │◄───►│ 本地仓库 │
└────────┘     └────┬───┘

              ┌─────┴─────┐
              │           │
           ┌──▼──┐    ┌───▼──┐
           │ 开发者A │    │ 开发者B │
           └─────┘    └──────┘

仓库(Repository)

仓库是 Git 管理项目的核心概念,它包含:

  • 项目文件(代码、文档等)
  • 完整的修改历史
  • 配置信息

规范

提交信息规范

遵循约定式提交(Conventional Commits)规范:

bash
<type>(<scope>): <subject>

<body>

<footer>

type 类型说明:

类型说明
feat新功能
fix修复 bug
docs文档变更
style代码格式(不影响代码运行的变动)
refactor重构(既不是新增功能,也不是修改 bug)
test增加测试
chore构建过程或辅助工具的变动

示例:

bash
feat(user): 添加用户登录功能

- 实现用户名密码登录
- 添加登录状态保持
- 集成第三方登录

Closes #123

分支命名规范:

bash
feature/功能名称    # 新功能分支
bugfix/问题描述     # bug 修复分支
hotfix/紧急修复     # 紧急修复分支
release/版本号      # 发布分支

1.3 原理深度解析

Git 的存储机制

Git 的核心是一个简单的键值对数据库。你可以向该数据库插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回内容。

Git 对象模型:

Git 有四种主要的对象类型:

  1. Blob(二进制大对象)

    • 存储文件内容
    • 不包含文件名,只包含内容
  2. Tree(树对象)

    • 存储目录结构
    • 包含文件名和权限信息
    • 指向 blob 或其他 tree
  3. Commit(提交对象)

    • 存储提交信息
    • 指向一个 tree(项目根目录)
    • 包含父提交、作者、时间等信息
  4. Tag(标签对象)

    • 存储标签信息
    • 指向一个 commit
Commit 对象
├── tree (根目录)
│   ├── blob (文件1内容)
│   ├── blob (文件2内容)
│   └── tree (子目录)
│       └── blob (文件3内容)
├── parent (父提交)
├── author (作者)
├── committer (提交者)
└── message (提交信息)

SHA-1 哈希:

每个 Git 对象都有一个 40 位的 SHA-1 哈希值作为唯一标识:

bash
# 查看对象的哈希值
$ echo "Hello, Git" | git hash-object --stdin
8d01495a85a9f6e2f8e9bc4b5a5c5a5a5a5a5a5a

Git 的三个工作区域

Git 有三个重要的工作区域,理解它们对于掌握 Git 至关重要:

┌─────────────────────────────────────────────────────────┐
│                        Git 工作流程                       │
├─────────────────────────────────────────────────────────┤
│                                                          │
│   ┌──────────┐    git add    ┌──────────┐   git commit  │
│   │  工作目录  │ ───────────► │   暂存区   │ ───────────► │
│   │ (Working) │              │  (Staging)│              │
│   └──────────┘              └──────────┘              │
│        ▲                                                    │
│        │                    ┌──────────┐                  │
│        │    git checkout    │   仓库    │                  │
│        └────────────────────│(Repository)│◄─────────────┘
│                             └──────────┘                  │
│                                                          │
└─────────────────────────────────────────────────────────┘
  1. 工作目录(Working Directory)

    • 你实际编辑文件的地方
    • 包含项目的当前状态
    • 可以是新建、修改或删除的文件
  2. 暂存区(Staging Area / Index)

    • 下次提交的文件快照
    • 使用 git add 命令将文件添加到这里
    • 是一个文件,保存了即将提交的文件信息
  3. 仓库(Repository)

    • 保存所有提交的历史记录
    • 使用 git commit 将暂存区内容保存到这里
    • 每个提交都是一个完整的项目快照

Git 分支的本质

Git 的分支本质上仅仅是指向提交对象的可变指针。Git 默认分支名称是 mainmaster

bash
# 分支只是指向某个 commit 的指针
main ──► commit A ──► commit B ──► commit C

feature ──────────────────────────────┘

创建分支就是创建一个新的指针:

bash
$ git branch feature
# 创建了一个名为 feature 的新指针,指向当前 commit

HEAD 是一个特殊指针,指向当前所在的本地分支:

bash
$ git checkout feature
# HEAD 现在指向 feature 分支

HEAD ──► feature ──► commit C
              main ──► commit C

1.4 常见错误与踩坑点

错误 1:提交了敏感信息

错误表现:

bash
$ git log
commit abc123...
Author: user@example.com
Date:   Mon Jan 1 10:00:00 2024 +0800

    添加配置文件

    包含数据库密码和 API 密钥

产生原因:

  • 不小心将包含密码、密钥等敏感信息的文件提交到仓库
  • 没有使用 .gitignore 文件过滤敏感文件

解决方案:

  1. 使用 .gitignore 文件排除敏感文件:
bash
# .gitignore
.env
config/local.php
*.key
*.pem
  1. 如果已经提交,需要从历史记录中彻底删除:
bash
# 使用 git filter-branch 删除敏感文件
$ git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch path/to/sensitive-file' \
  --prune-empty --tag-name-filter cat -- --all

# 强制推送(谨慎使用)
$ git push origin --force --all
  1. 更好的方法是使用 git-secretspre-commit 钩子自动检测:
bash
# 安装 git-secrets
$ brew install git-secrets

# 在仓库中设置
$ git secrets --install
$ git secrets --register-aws

错误 2:错误的提交信息

错误表现:

bash
$ git log --oneline
abc1234 修复 bug
def5678 更新
ghi9012 修改文件

产生原因:

  • 提交信息过于简单,无法理解修改内容
  • 没有遵循提交信息规范

解决方案:

  1. 修改最后一次提交信息:
bash
$ git commit --amend -m "fix(user): 修复用户登录验证失败的问题

- 修复密码验证逻辑错误
- 添加输入验证

Closes #456"
  1. 修改历史提交信息:
bash
# 使用交互式 rebase
$ git rebase -i HEAD~3

# 将要修改的 commit 前面的 pick 改为 reword
reword abc1234 修复 bug
pick def5678 更新
pick ghi9012 修改文件

# 保存后会打开编辑器修改每个提交信息

错误 3:在错误的分支上工作

错误表现:

bash
$ git status
On branch main
Changes to be committed:
  new file:   feature.php

产生原因:

  • 忘记创建新分支就开始开发
  • 忘记切换到正确的分支

解决方案:

  1. 如果还没有提交,可以轻松转移:
bash
# 创建新分支并转移修改
$ git checkout -b feature/new-feature
  1. 如果已经提交,使用 cherry-pick:
bash
# 在正确的分支上应用提交
$ git checkout feature/new-feature
$ git cherry-pick abc1234

# 删除错误分支上的提交
$ git checkout main
$ git reset --hard HEAD~1

错误 4:合并冲突处理不当

错误表现:

bash
$ git merge feature
Auto-merging config.php
CONFLICT (content): Merge conflict in config.php
Automatic merge failed; fix conflicts and then commit the result.

产生原因:

  • 多人修改同一文件的同一位置
  • 分支差异过大

解决方案:

  1. 查看冲突文件:
bash
$ git status
Unmerged paths:
  both modified:   config.php
  1. 手动解决冲突:
php
<?php
// config.php
<<<<<<< HEAD
$db_host = 'localhost';
$db_name = 'production';
=======
$db_host = '127.0.0.1';
$db_name = 'development';
>>>>>>> feature

修改为:

php
<?php
// config.php
$db_host = 'localhost';
$db_name = 'production';
  1. 标记为已解决:
bash
$ git add config.php
$ git commit -m "merge: 合并 feature 分支,解决配置文件冲突"

错误 5:误删文件或提交

错误表现:

bash
$ git rm important.php
$ git commit -m "删除文件"
# 发现删错了

产生原因:

  • 操作失误
  • 没有确认就执行删除操作

解决方案:

  1. 如果还没有提交:
bash
# 恢复误删的文件
$ git checkout -- important.php
  1. 如果已经提交:
bash
# 查看历史找到删除前的提交
$ git log --oneline

# 恢复特定文件
$ git checkout abc1234 -- important.php
$ git commit -m "restore: 恢复误删的 important.php"
  1. 使用 git reflog 找回丢失的提交:
bash
$ git reflog
abc1234 HEAD@{0}: reset: moving to HEAD~1
def5678 HEAD@{1}: commit: 重要的提交

# 恢复到之前的状态
$ git reset --hard def5678

1.5 常见应用场景

场景 1:初始化新项目

场景描述: 开始一个全新的项目,需要使用 Git 进行版本控制。

使用方法:

  1. 创建项目目录
  2. 初始化 Git 仓库
  3. 创建初始文件
  4. 进行首次提交

示例代码:

bash
# 创建项目目录
$ mkdir my-project
$ cd my-project

# 初始化 Git 仓库
$ git init
Initialized empty Git repository in /path/my-project/.git/

# 创建 .gitignore 文件
$ cat > .gitignore << 'EOF'
/vendor/
/node_modules/
.env
.DS_Store
*.log
EOF

# 创建 README 文件
$ echo "# My Project" > README.md

# 添加所有文件到暂存区
$ git add .

# 查看状态
$ git status
On branch main

No commits yet

Changes to be committed:
  new file:   .gitignore
  new file:   README.md

# 进行首次提交
$ git commit -m "feat: 初始化项目

- 添加 .gitignore 配置
- 添加 README 文件"

[main (root-commit) abc1234] feat: 初始化项目
 2 files changed, 10 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 README.md

# 查看提交历史
$ git log --oneline
abc1234 feat: 初始化项目

运行结果分析:

  • git init 创建了 .git 目录,存储所有版本信息
  • .gitignore 文件排除了不需要版本控制的文件
  • 首次提交建立了项目的基础结构

场景 2:克隆远程仓库

场景描述: 参与已有项目开发,需要从远程服务器获取代码。

使用方法:

  1. 使用 git clone 命令
  2. 配置个人信息
  3. 查看项目结构

示例代码:

bash
# 克隆远程仓库
$ git clone https://github.com/laravel/laravel.git my-laravel
Cloning into 'my-laravel'...
remote: Enumerating objects: 12345, done.
remote: Counting objects: 100% (12345/12345), done.
remote: Compressing objects: 100% (6789/6789), done.
remote: Total 12345 (delta 5678), reused 12345 (delta 5678), pack-reused 0
Receiving objects: 100% (12345/12345), 5.67 MiB | 1.23 MiB/s, done.
Resolving deltas: 100% (5678/5678), done.

# 进入项目目录
$ cd my-laravel

# 查看远程仓库信息
$ git remote -v
origin  https://github.com/laravel/laravel.git (fetch)
origin  https://github.com/laravel/laravel.git (push)

# 查看分支
$ git branch -a
* main
  remotes/origin/HEAD -> origin/main
  remotes/origin/main
  remotes/origin/develop

# 配置个人信息
$ git config user.name "Your Name"
$ git config user.email "your.email@example.com"

运行结果分析:

  • git clone 自动创建远程仓库的完整副本
  • 自动设置了 origin 远程仓库别名
  • 可以直接开始开发工作

场景 3:日常开发流程

场景描述: 日常开发中最常见的工作流程:修改代码、提交更改、同步远程。

使用方法:

  1. 拉取最新代码
  2. 创建功能分支
  3. 修改代码
  4. 提交更改
  5. 推送到远程

示例代码:

bash
# 拉取最新代码
$ git pull origin main
From https://github.com/user/repo
 * branch            main       -> FETCH_HEAD
Already up to date.

# 创建功能分支
$ git checkout -b feature/user-auth
Switched to a new branch 'feature/user-auth'

# 修改文件
$ echo "<?php // 用户认证模块" > Auth.php

# 查看修改状态
$ git status
On branch feature/user-auth
Untracked files:
  Auth.php

# 查看具体修改
$ git diff

# 添加到暂存区
$ git add Auth.php

# 提交更改
$ git commit -m "feat(auth): 添加用户认证模块基础结构"
[feature/user-auth def5678] feat(auth): 添加用户认证模块基础结构
 1 file changed, 1 insertion(+)
 create mode 100644 Auth.php

# 推送到远程
$ git push -u origin feature/user-auth
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 345 bytes | 345.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
remote:
remote: Create a pull request for 'feature/user-auth' on GitHub by visiting:
remote:      https://github.com/user/repo/pull/new/feature/user-auth
remote:
To https://github.com/user/repo.git
 * [new branch]      feature/user-auth -> feature/user-auth
Branch 'feature/user-auth' set up to track remote branch 'feature/user-auth' from 'origin'.

运行结果分析:

  • 使用分支隔离开发,不影响主分支
  • 每次提交都是原子操作,便于回滚
  • 推送后可以在 GitHub 创建 Pull Request

场景 4:查看项目历史

场景描述: 需要了解项目的开发历史,追踪某个功能的演进过程。

使用方法:

  1. 查看提交历史
  2. 查看文件修改历史
  3. 查看特定提交详情

示例代码:

bash
# 查看简洁的提交历史
$ git log --oneline
ghi9012 feat: 添加用户权限管理
def5678 fix: 修复登录验证 bug
abc1234 feat: 添加用户登录功能
xyz5678 docs: 更新 README

# 查看详细的提交历史
$ git log
commit ghi9012345678901234567890123456789012345
Author: Zhang San <zhangsan@example.com>
Date:   Mon Jan 15 14:30:00 2024 +0800

    feat: 添加用户权限管理
    
    - 实现角色管理
    - 实现权限分配
    - 添加权限验证中间件

# 查看图形化历史
$ git log --oneline --graph --all
*   ghi9012 Merge branch 'feature/permissions'
|\
| * def5678 feat: 添加权限管理
* | abc1234 feat: 添加用户登录功能
|/
* xyz5678 docs: 更新 README

# 查看某个文件的历史
$ git log --follow --oneline config/database.php
abc1234 feat: 添加数据库配置
xyz5678 chore: 初始化项目

# 查看某次提交的详细修改
$ git show abc1234
commit abc1234567890abcdef...
Author: Zhang San <zhangsan@example.com>
Date:   Mon Jan 10 10:00:00 2024 +0800

    feat: 添加用户登录功能

diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php
new file mode 100644
index 0000000..abc1234
--- /dev/null
+++ b/app/Http/Controllers/AuthController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Http\Controllers;
+
+class AuthController extends Controller
+{
+    public function login()
+    {
+        // 登录逻辑
+    }
+}

运行结果分析:

  • --oneline 显示简洁的单行历史
  • --graph 显示分支合并图形
  • --follow 可以追踪文件重命名

场景 5:团队协作合并代码

场景描述: 多人协作开发,需要合并不同成员的代码更改。

使用方法:

  1. 获取远程更新
  2. 合并分支
  3. 解决冲突
  4. 推送合并结果

示例代码:

bash
# 切换到主分支
$ git checkout main
Switched to branch 'main'

# 拉取最新代码
$ git pull origin main
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 5 (delta 2), reused 5 (delta 2), pack-reused 0
Unpacking objects: 100% (5/5), done.
From https://github.com/user/repo
 * branch            main       -> FETCH_HEAD
   abc1234..def5678  main       -> origin/main
Updating abc1234..def5678
Fast-forward
 app/Models/User.php | 5 +++++
 1 file changed, 5 insertions(+)

# 合并功能分支
$ git merge feature/user-auth
Updating def5678..ghi9012
Fast-forward
 app/Http/Controllers/AuthController.php | 45 +++++++++++++++++++++++++
 routes/web.php                          |  3 ++
 2 files changed, 48 insertions(+)
 create mode 100644 app/Http/Controllers/AuthController.php

# 如果有冲突,手动解决
$ git merge feature/user-profile
Auto-merging app/Models/User.php
CONFLICT (content): Merge conflict in app/Models/User.php
Automatic merge failed; fix conflicts and then commit the result.

# 查看冲突文件
$ git status
Unmerged paths:
  both modified:   app/Models/User.php

# 编辑冲突文件
$ vim app/Models/User.php
# 解决冲突后标记为已解决
$ git add app/Models/User.php

# 完成合并
$ git commit -m "merge: 合并用户资料功能分支"
[main 1234567] merge: 合并用户资料功能分支

# 推送到远程
$ git push origin main
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 1.23 KiB | 1.23 MiB/s, done.
Total 8 (delta 4), reused 0 (delta 0), pack-reused 0
To https://github.com/user/repo.git
   def5678..1234567  main -> main

运行结果分析:

  • git pull 先获取远程更新再合并
  • Fast-forward 合并是最简单的合并方式
  • 冲突需要手动解决后才能完成合并

1.6 企业级进阶应用

Git Flow 工作流

Git Flow 是一种广泛使用的工作流模型,适合有计划发布周期的项目。

main ──────●──────●──────●──────►
           │      │      │
release ───┼──────●──────┘
           │      │
develop ───●──────●──────●──────●──────►
           │             │      │
feature ───●─────────────●      │
           │                    │
hotfix ────┼────────────────────●

分支类型:

分支类型命名规则说明
mainmain/master生产环境代码
developdevelop开发主分支
featurefeature/*新功能开发
releaserelease/*发布准备
hotfixhotfix/*紧急修复

实现示例:

bash
# 安装 git-flow
$ brew install git-flow

# 初始化 git-flow
$ git flow init

# 开始新功能
$ git flow feature start user-auth
Switched to a new branch 'feature/user-auth'

# 完成功能开发
$ git flow feature finish user-auth
Switched to branch 'develop'
Already up to date!
Deleted branch feature/user-auth (was abc1234).

# 开始发布
$ git flow release start v1.0.0
Switched to a new branch 'release/v1.0.0'

# 完成发布
$ git flow release finish v1.0.0
Switched to branch 'main'
Merge made by the 'recursive' strategy.
Switched to branch 'develop'
Merge made by the 'recursive' strategy.
Deleted branch release/v1.0.0 (was def5678).

# 紧急修复
$ git flow hotfix start critical-bug
$ git flow hotfix finish critical-bug

Git Hooks 自动化

Git Hooks 可以在特定事件发生时自动执行脚本。

常用 Hooks:

Hook触发时机用途
pre-commit提交前代码检查、格式化
commit-msg提交信息编辑后验证提交信息格式
pre-push推送前运行测试
post-merge合并后安装依赖

pre-commit 示例:

bash
#!/bin/bash
# .git/hooks/pre-commit

# 检查 PHP 语法错误
echo "检查 PHP 语法..."
FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')
if [ -n "$FILES" ]; then
    for FILE in $FILES; do
        php -l "$FILE" > /dev/null 2>&1
        if [ $? -ne 0 ]; then
            echo "PHP 语法错误: $FILE"
            exit 1
        fi
    done
fi

# 检查代码风格
echo "检查代码风格..."
./vendor/bin/phpcs --standard=PSR12 $FILES
if [ $? -ne 0 ]; then
    echo "代码风格检查失败,请修复后再提交"
    exit 1
fi

echo "检查通过!"
exit 0

commit-msg 示例:

bash
#!/bin/bash
# .git/hooks/commit-msg

# 获取提交信息
COMMIT_MSG=$(cat "$1")

# 验证提交信息格式
PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
    echo "提交信息格式错误!"
    echo "正确格式: type(scope): subject"
    echo "示例: feat(user): 添加用户登录功能"
    exit 1
fi

Git 大文件管理

对于大型二进制文件(如图片、视频),使用 Git LFS(Large File Storage)。

bash
# 安装 Git LFS
$ brew install git-lfs

# 初始化
$ git lfs install

# 追踪大文件类型
$ git lfs track "*.psd"
$ git lfs track "*.mp4"
$ git lfs track "*.zip"

# 查看追踪规则
$ git lfs track
Listing tracked patterns
    *.psd (.gitattributes)
    *.mp4 (.gitattributes)
    *.zip (.gitattributes)

# 正常提交
$ git add .
$ git commit -m "feat: 添加设计稿"

1.7 行业最佳实践

实践 1:频繁提交

实践内容: 每次完成一个小功能或修复一个 bug 就提交,而不是积累大量修改后一次性提交。

推荐理由:

  • 每个提交都是原子操作,便于回滚和审查
  • 提交历史更清晰,便于理解项目演进
  • 减少代码冲突的风险
bash
# 好的做法:小步提交
$ git commit -m "feat(auth): 添加登录表单验证"
$ git commit -m "feat(auth): 实现登录 API 接口"
$ git commit -m "feat(auth): 添加登录状态保持"

# 不好的做法:大提交
$ git commit -m "feat: 完成整个用户系统(登录、注册、权限、个人中心)"

实践 2:使用分支进行功能开发

实践内容: 每个新功能、bug 修复都在独立分支上进行,完成后合并到主分支。

推荐理由:

  • 隔离开发,不影响主分支稳定性
  • 便于代码审查和讨论
  • 可以并行开发多个功能
bash
# 功能分支工作流
$ git checkout -b feature/payment
# ... 开发支付功能 ...
$ git push origin feature/payment
# 创建 Pull Request 进行代码审查
# 审查通过后合并到 main

实践 3:编写清晰的提交信息

实践内容: 遵循约定式提交规范,编写清晰、详细的提交信息。

推荐理由:

  • 便于理解每次提交的目的
  • 自动生成变更日志
  • 便于追踪问题
bash
# 好的提交信息
feat(order): 添加订单导出功能

- 支持 CSV Excel 格式导出
- 支持按日期范围筛选
- 支持批量导出

Breaking Change: 导出接口参数格式变更

Closes #789

# 不好的提交信息
fix bug
update
修改

实践 4:定期同步远程更新

实践内容: 每天开始工作前,先拉取远程仓库的最新更新。

推荐理由:

  • 及时了解团队其他成员的进度
  • 减少合并冲突
  • 保持本地仓库与远程同步
bash
# 每日开始工作
$ git checkout main
$ git pull origin main
$ git checkout feature/my-feature
$ git rebase main  # 或 merge

实践 5:使用 .gitignore 过滤文件

实践内容: 创建完善的 .gitignore 文件,排除不需要版本控制的文件。

推荐理由:

  • 保持仓库干净
  • 减少仓库体积
  • 避免提交敏感信息
bash
# PHP 项目 .gitignore 示例
/vendor/
/node_modules/
/.idea/
/.vscode/
/.env
/.env.local
*.log
*.cache
.DS_Store
Thumbs.db

# 编译输出
/build/
/dist/

# 测试覆盖率
/coverage/
/.phpunit.result.cache

1.8 常见问题答疑(FAQ)

问题 1:Git 和 GitHub 有什么区别?

问题描述: 很多初学者混淆 Git 和 GitHub,认为它们是同一个东西。

回答内容:

Git 和 GitHub 是两个不同的概念:

Git 是一个分布式版本控制软件,安装在你的本地电脑上,用于管理代码版本。它是一个工具,就像 Word 是编辑文档的工具一样。

GitHub 是一个代码托管平台,运行在云端服务器上,提供 Git 仓库的远程存储服务。它是一个网站,就像百度网盘是存储文件的网站一样。

类比理解:

  • Git 就像你电脑上的 Word 软件
  • GitHub 就像 Google Docs 或石墨文档
  • 你可以用 Word 本地编辑文档(用 Git 本地管理代码)
  • 也可以把文档上传到 Google Docs 协作(把代码推送到 GitHub 协作)

其他 Git 托管平台:

  • GitLab
  • Bitbucket
  • Gitee(码云)
  • 自建 Git 服务器(如 Gogs、Gitea)

示例代码:

bash
# Git 是本地工具
$ git init                    # 本地创建仓库
$ git add .                   # 本地添加文件
$ git commit -m "message"     # 本地提交

# GitHub 是远程平台
$ git remote add origin https://github.com/user/repo.git  # 连接远程仓库
$ git push origin main        # 推送到 GitHub
$ git pull origin main        # 从 GitHub 拉取更新

问题 2:什么时候用 merge,什么时候用 rebase?

问题描述: 合并分支时,不清楚应该选择 merge 还是 rebase。

回答内容:

Merge(合并):

  • 创建一个新的合并提交
  • 保留完整的分支历史
  • 历史图会有分叉

Rebase(变基):

  • 将提交移动到目标分支顶端
  • 创建线性历史
  • 历史图是一条直线

选择原则:

场景推荐方式原因
合并公共分支到功能分支merge保留完整历史,便于追溯
更新功能分支与主分支同步rebase保持历史线性,便于理解
已推送到远程的分支mergerebase 会改写历史,影响他人
本地未推送的分支rebase可以安全地改写历史

示例代码:

bash
# 使用 merge
$ git checkout feature
$ git merge main
# 结果:
# *   Merge branch 'main' into feature
# |\
# | * commit on main
# * | commit on feature
# |/
# * common ancestor

# 使用 rebase
$ git checkout feature
$ git rebase main
# 结果:
# * commit on feature (rebased)
# * commit on feature (rebased)
# * commit on main
# * common ancestor

重要警告: 永远不要对已推送到远程的提交执行 rebase!这会改写历史,导致其他开发者的仓库出现问题。

问题 3:如何撤销最近的提交?

问题描述: 提交后发现有问题,想要撤销这次提交。

回答内容:

根据不同情况,有三种撤销方式:

1. 保留修改,只撤销提交(推荐):

bash
$ git reset --soft HEAD~1
# 撤销最近一次提交,保留修改在暂存区
# 可以重新编辑后再次提交

2. 保留修改,撤销提交和暂存:

bash
$ git reset --mixed HEAD~1
# 或简写为
$ git reset HEAD~1
# 撤销提交和暂存,保留修改在工作目录
# 需要重新 add 和 commit

3. 完全撤销,丢弃所有修改(危险):

bash
$ git reset --hard HEAD~1
# 撤销提交,丢弃所有修改
# ⚠️ 警告:修改将永久丢失!

如果已经推送到远程:

bash
# 创建一个反向提交(安全)
$ git revert HEAD
# 这会创建一个新的提交,撤销上一次提交的修改
# 适合已推送的情况,不会影响他人

# 推送到远程
$ git push origin main

对比分析:

方式提交历史文件修改适用场景
reset --soft撤销保留在暂存区想修改提交信息
reset --mixed撤销保留在工作目录想重新组织提交
reset --hard撤销丢弃确认要放弃修改
revert新增反向提交撤销效果已推送的提交

问题 4:如何解决 "fatal: refusing to merge unrelated histories" 错误?

问题描述: 合并两个没有共同祖先的分支时出现此错误。

回答内容:

这个错误发生在两个仓库没有共同的提交历史时,Git 默认拒绝合并以防止意外合并无关项目。

常见场景:

  • 本地新建仓库后,想关联远程仓库
  • 合并两个独立创建的仓库

解决方案:

bash
# 方法 1:允许合并不相关历史
$ git pull origin main --allow-unrelated-histories
# 或
$ git merge origin/main --allow-unrelated-histories

# 方法 2:推荐做法 - 先克隆再添加文件
$ git clone https://github.com/user/repo.git
$ cd repo
# 然后复制你的文件到这个目录
$ git add .
$ git commit -m "feat: 添加项目文件"
$ git push origin main

# 方法 3:如果本地已有项目,关联远程仓库
$ git remote add origin https://github.com/user/repo.git
$ git fetch origin
$ git merge origin/main --allow-unrelated-histories
$ git push -u origin main

最佳实践: 建议使用方法 2,先克隆远程仓库,再添加本地文件,这样可以避免历史不相关的问题。

问题 5:如何只提交文件的部分修改?

问题描述: 一个文件有多处修改,只想提交其中一部分。

回答内容:

Git 提供了交互式暂存功能,可以选择性地暂存文件的修改片段。

使用方法:

bash
# 交互式添加
$ git add -p filename.php

# Git 会显示每个修改块,让你选择
# y - 暂存此块
# n - 不暂存此块
# q - 退出
# a - 暂存此文件所有剩余块
# d - 不暂存此文件所有剩余块
# s - 分割成更小的块
# e - 手动编辑
# ? - 帮助

示例场景:

php
<?php
// 原文件 content.php
function oldFunction() {
    return 'old';
}

// 修改后的文件
function newFunction() {  // 修改 1:想提交
    return 'new';
}

// TODO: 临时调试代码    // 修改 2:不想提交
$debug = true;

操作步骤:

bash
$ git add -p content.php
diff --git a/content.php b/content.php
index abc1234..def5678 100644
--- a/content.php
+++ b/content.php
@@ -1,5 +1,8 @@
-function oldFunction() {
-    return 'old';
+function newFunction() {
+    return 'new';
 }
+
+// TODO: 临时调试代码
+$debug = true;

(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? s
# 分割成更小的块

Split into 2 hunks.

@@ -1,5 +1,5 @@
-function oldFunction() {
-    return 'old';
+function newFunction() {
+    return 'new';
 }
(1/2) Stage this hunk [y,n,q,a,d,e,?]? y
# 暂存第一个修改

@@ -3,3 +3,6 @@
 }
+
+// TODO: 临时调试代码
+$debug = true;
(2/2) Stage this hunk [y,n,q,a,d,e,?]? n
# 不暂存第二个修改

$ git status
Changes to be committed:
  modified:   content.php  # 只包含第一个修改

Changes not staged for commit:
  modified:   content.php  # 包含第二个修改

问题 6:如何查看某个文件的修改历史?

问题描述: 需要追踪某个文件是谁在什么时候修改的。

回答内容:

Git 提供了多种方式查看文件历史:

1. 查看文件的提交历史:

bash
$ git log --follow --oneline filename.php
abc1234 feat: 添加新功能
def5678 fix: 修复 bug
ghi9012 refactor: 重构代码
xyz5678 feat: 初始提交

# --follow 可以追踪文件重命名

2. 查看每次提交的具体修改:

bash
$ git log -p filename.php
# 显示每次提交的详细差异

3. 查看每行代码的最后修改信息:

bash
$ git blame filename.php
abc1234 (Zhang San 2024-01-15 10:00:00 +0800  1) <?php
def5678 (Li Si    2024-01-14 15:30:00 +0800  2) 
ghi9012 (Wang Wu  2024-01-13 09:00:00 +0800  3) class UserService
abc1234 (Zhang San 2024-01-15 10:00:00 +0800  4) {
abc1234 (Zhang San 2024-01-15 10:00:00 +0800  5)     public function login()
# 格式:提交哈希 (作者 时间 行号) 代码内容

4. 查看特定行范围的修改:

bash
$ git blame -L 10,20 filename.php
# 只查看第 10-20 行的修改信息

5. 查看文件在某个时间点的状态:

bash
$ git show abc1234:filename.php
# 查看某次提交时的文件内容

$ git show HEAD~3:filename.php
# 查看 3 次提交前的文件内容

6. 使用图形化工具:

bash
$ gitk filename.php
# 打开图形化历史查看器

1.9 实战练习

基础练习:创建第一个 Git 仓库

练习目标: 掌握 Git 仓库的创建、文件添加和提交操作。

解题思路:

  1. 创建项目目录
  2. 初始化 Git 仓库
  3. 创建文件
  4. 添加到暂存区
  5. 提交更改

常见误区:

  • 忘记初始化仓库
  • 没有添加文件就提交
  • 提交信息过于简单

分步提示:

bash
# 步骤 1:创建项目目录
$ mkdir my-first-repo
$ cd my-first-repo

# 步骤 2:初始化 Git 仓库
# 提示:使用 git init 命令

# 步骤 3:创建 README.md 文件
# 提示:写入项目名称和简介

# 步骤 4:查看仓库状态
# 提示:使用 git status 命令

# 步骤 5:添加文件到暂存区
# 提示:使用 git add 命令

# 步骤 6:提交更改
# 提示:使用 git commit 命令,写一个有意义的提交信息

# 步骤 7:查看提交历史
# 提示:使用 git log 命令

参考代码:

bash
# 创建项目目录
$ mkdir my-first-repo
$ cd my-first-repo

# 初始化 Git 仓库
$ git init
Initialized empty Git repository in /path/my-first-repo/.git/

# 创建 README.md
$ cat > README.md << 'EOF'
# My First Repository

这是我的第一个 Git 仓库项目。

## 功能

- 学习 Git 基础操作
- 练习版本控制

## 安装

```bash
git clone https://github.com/username/my-first-repo.git

EOF

查看状态

$ git status On branch main

No commits yet

Untracked files: README.md

添加到暂存区

$ git add README.md

再次查看状态

$ git status On branch main

No commits yet

Changes to be committed: new file: README.md

提交更改

$ git commit -m "feat: 初始化项目,添加 README 文件" [main (root-commit) abc1234] feat: 初始化项目,添加 README 文件 1 file changed, 15 insertions(+) create mode 100644 README.md

查看提交历史

$ git log commit abc1234567890abcdef1234567890abcdef12345678 Author: Your Name your.email@example.com Date: Mon Jan 15 10:00:00 2024 +0800

feat: 初始化项目,添加 README 文件

#### 进阶练习:分支管理实战

**练习目标:**
掌握分支创建、切换、合并和删除操作。

**解题思路:**
1. 创建主分支的初始提交
2. 创建并切换到功能分支
3. 在功能分支开发新功能
4. 切换回主分支
5. 合并功能分支
6. 删除功能分支

**常见误区:**
- 忘记切换分支就开始开发
- 合并前没有提交功能分支的更改
- 合并后忘记删除功能分支

**分步提示:**

```bash
# 步骤 1:创建初始项目
$ mkdir branch-practice
$ cd branch-practice
$ git init
$ echo "# Branch Practice" > README.md
$ git add .
$ git commit -m "feat: 初始化项目"

# 步骤 2:创建并切换到 feature/login 分支
# 提示:使用 git checkout -b 命令

# 步骤 3:在功能分支创建登录相关文件
# 提示:创建 LoginController.php

# 步骤 4:提交功能分支的更改

# 步骤 5:切换回 main 分支
# 提示:使用 git checkout 命令

# 步骤 6:合并 feature/login 分支
# 提示:使用 git merge 命令

# 步骤 7:删除功能分支
# 提示:使用 git branch -d 命令

# 步骤 8:查看分支历史
# 提示:使用 git log --oneline --graph

参考代码:

bash
# 创建初始项目
$ mkdir branch-practice
$ cd branch-practice
$ git init
$ echo "# Branch Practice" > README.md
$ git add .
$ git commit -m "feat: 初始化项目"
[main (root-commit) abc1234] feat: 初始化项目
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

# 创建并切换到功能分支
$ git checkout -b feature/login
Switched to a new branch 'feature/login'

# 创建登录控制器
$ mkdir -p app/Controllers
$ cat > app/Controllers/LoginController.php << 'EOF'
<?php

namespace App\Controllers;

class LoginController
{
    public function index()
    {
        return 'Login Page';
    }

    public function authenticate()
    {
        // 认证逻辑
        return 'Authenticating...';
    }
}
EOF

# 提交功能分支的更改
$ git add .
$ git commit -m "feat(login): 添加登录控制器"
[feature/login def5678] feat(login): 添加登录控制器
 1 file changed, 17 insertions(+)
 create mode 100644 app/Controllers/LoginController.php

# 切换回 main 分支
$ git checkout main
Switched to branch 'main'

# 合并功能分支
$ git merge feature/login
Updating abc1234..def5678
Fast-forward
 app/Controllers/LoginController.php | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 app/Controllers/LoginController.php

# 删除功能分支
$ git branch -d feature/login
Deleted branch feature/login (was def5678).

# 查看分支历史
$ git log --oneline --graph
* def5678 feat(login): 添加登录控制器
* abc1234 feat: 初始化项目

# 查看当前分支
$ git branch
* main

挑战练习:解决复杂的合并冲突

练习目标: 模拟真实的多人协作场景,解决复杂的合并冲突。

解题思路:

  1. 创建模拟场景
  2. 在两个分支修改同一文件
  3. 尝试合并产生冲突
  4. 分析冲突内容
  5. 手动解决冲突
  6. 完成合并

常见误区:

  • 不理解冲突标记的含义
  • 解决冲突时丢失代码
  • 解决冲突后忘记测试

分步提示:

bash
# 步骤 1:创建项目并初始化
$ mkdir conflict-practice
$ cd conflict-practice
$ git init

# 步骤 2:创建配置文件并提交
$ cat > config.php << 'EOF'
<?php
return [
    'app_name' => 'My App',
    'debug' => false,
    'database' => [
        'host' => 'localhost',
        'name' => 'myapp',
    ],
];
EOF
$ git add .
$ git commit -m "feat: 添加配置文件"

# 步骤 3:创建 feature/database 分支并修改配置
$ git checkout -b feature/database
# 修改 database 配置
# 提交更改

# 步骤 4:切换回 main 分支并修改同一位置
$ git checkout main
# 修改 database 配置(不同的值)
# 提交更改

# 步骤 5:尝试合并 feature/database 分支
# 提示:观察冲突信息

# 步骤 6:查看冲突文件内容
# 提示:理解 <<<<<<< HEAD 和 ======= 标记

# 步骤 7:手动解决冲突
# 提示:保留正确的配置,删除冲突标记

# 步骤 8:标记为已解决并完成合并
# 提示:git add 和 git commit

# 步骤 9:验证最终结果

参考代码:

bash
# 创建项目
$ mkdir conflict-practice
$ cd conflict-practice
$ git init

# 创建初始配置文件
$ cat > config.php << 'EOF'
<?php
return [
    'app_name' => 'My App',
    'debug' => false,
    'database' => [
        'host' => 'localhost',
        'name' => 'myapp',
    ],
];
EOF
$ git add .
$ git commit -m "feat: 添加配置文件"
[main (root-commit) abc1234] feat: 添加配置文件
 1 file changed, 10 insertions(+)
 create mode 100644 config.php

# 创建功能分支并修改
$ git checkout -b feature/database
Switched to a new branch 'feature/database'

$ cat > config.php << 'EOF'
<?php
return [
    'app_name' => 'My App',
    'debug' => false,
    'database' => [
        'host' => 'db.example.com',
        'name' => 'production_db',
        'port' => 3306,
    ],
];
EOF
$ git add .
$ git commit -m "feat(database): 更新生产环境数据库配置"
[feature/database def5678] feat(database): 更新生产环境数据库配置
 1 file changed, 3 insertions(+), 2 deletions(-)

# 切换回 main 分支并修改同一位置
$ git checkout main
Switched to branch 'main'

$ cat > config.php << 'EOF'
<?php
return [
    'app_name' => 'My App',
    'debug' => true,
    'database' => [
        'host' => '127.0.0.1',
        'name' => 'development_db',
    ],
];
EOF
$ git add .
$ git commit -m "feat: 更新开发环境配置"
[main ghi9012] feat: 更新开发环境配置
 1 file changed, 2 insertions(+), 2 deletions(-)

# 尝试合并
$ git merge feature/database
Auto-merging config.php
CONFLICT (content): Merge conflict in config.php
Automatic merge failed; fix conflicts and then commit the result.

# 查看冲突文件
$ cat config.php
<?php
return [
    'app_name' => 'My App',
<<<<<<< HEAD
    'debug' => true,
    'database' => [
        'host' => '127.0.0.1',
        'name' => 'development_db',
=======
    'debug' => false,
    'database' => [
        'host' => 'db.example.com',
        'name' => 'production_db',
        'port' => 3306,
>>>>>>> feature/database
    ],
];

# 手动解决冲突 - 保留最佳配置
$ cat > config.php << 'EOF'
<?php
return [
    'app_name' => 'My App',
    'debug' => env('APP_DEBUG', false),
    'database' => [
        'host' => env('DB_HOST', 'localhost'),
        'name' => env('DB_NAME', 'myapp'),
        'port' => env('DB_PORT', 3306),
    ],
];
EOF

# 标记为已解决
$ git add config.php

# 完成合并
$ git commit -m "merge: 合并数据库配置,使用环境变量
- 解决 feature/database 和 main 的配置冲突
- 使用 env() 函数支持多环境配置"
[main 1234567] merge: 合并数据库配置,使用环境变量

# 查看最终结果
$ cat config.php
<?php
return [
    'app_name' => 'My App',
    'debug' => env('APP_DEBUG', false),
    'database' => [
        'host' => env('DB_HOST', 'localhost'),
        'name' => env('DB_NAME', 'myapp'),
        'port' => env('DB_PORT', 3306),
    ],
];

# 查看合并历史
$ git log --oneline --graph
*   1234567 merge: 合并数据库配置,使用环境变量
|\
| * def5678 feat(database): 更新生产环境数据库配置
* | ghi9012 feat: 更新开发环境配置
|/
* abc1234 feat: 添加配置文件

1.10 知识点总结

核心要点

  1. Git 是分布式版本控制系统

    • 每个开发者都有完整的仓库副本
    • 不依赖中央服务器,支持离线工作
    • 速度快、安全性高
  2. 三个工作区域

    • 工作目录:实际编辑文件的地方
    • 暂存区:下次提交的文件快照
    • 仓库:保存所有提交历史
  3. 基本工作流程

    • 修改文件 → git add → git commit → git push
    • git pull → 解决冲突 → git push
  4. 分支是 Git 的核心特性

    • 分支是指向提交的指针
    • 创建和切换分支非常快速
    • 支持并行开发
  5. 提交信息规范

    • 遵循约定式提交格式
    • 清晰描述修改内容
    • 便于生成变更日志

易错点回顾

易错点正确做法
提交敏感信息使用 .gitignore 排除,使用钩子检测
提交信息不规范遵循约定式提交规范
在错误分支工作开发前确认当前分支
合并冲突处理不当理解冲突标记,保留正确代码
误删文件或提交使用 git reflog 恢复
对已推送的提交 rebase已推送的提交使用 merge 或 revert

1.11 拓展参考资料

官方文档

进阶学习路径

  1. Git 进阶命令

    • git stash:暂存工作区修改
    • git cherry-pick:选择性合并提交
    • git bisect:二分查找问题提交
    • git filter-branch:重写历史
  2. Git 工作流

    • Git Flow
    • GitHub Flow
    • GitLab Flow
    • Trunk Based Development
  3. Git 工具集成

    • IDE 集成(VS Code、PhpStorm)
    • GUI 工具(Sourcetree、GitKraken)
    • CI/CD 集成(GitHub Actions、GitLab CI)
  4. Git 服务器搭建

    • GitLab CE/EE
    • Gitea
    • Gogs

推荐资源


学习建议: Git 是一个需要通过大量实践才能熟练掌握的工具。建议在学习过程中:

  1. 每天使用 Git 管理自己的代码
  2. 参与开源项目,体验真实的协作流程
  3. 遇到问题时,先尝试自己解决,再查阅文档
  4. 定期复习高级命令,提升效率

下一知识点: 本教程 → Git 核心概念Git 工作原理