Git 版本控制工具详解
一、为什么需要 Git?
从实际开发场景切入:
- 场景 1:写代码改来改去,想回退到昨天的版本,却找不到旧文件;
- 场景 2:多人合作写项目,改同一个文件互相覆盖,代码越改越乱;
- 场景 3:想同时开发两个功能,改了登录的代码,注册的代码就没法测试。
Git 的核心作用:版本控制 + 多人协作,解决以上所有问题 —— 它就像代码的'时光机'+'协作神器',能记录每一次修改,还能让多人并行开发不冲突。
二、Git 核心概念
1. 分布式 vs 集中式
- 集中式(如 SVN):代码只有一个'中央仓库',所有人必须连网才能提交,中央仓库崩了就全完;
- 分布式(Git):每个人电脑上都有完整的仓库(本地仓库),连网后和远程仓库同步即可,离线也能写代码、提交修改。
2. Git 的 4 个核心区域
| 区域 | 通俗解释 | 对应操作 |
|---|---|---|
| 工作区 | 你平时写代码的文件夹(未打包) | 编辑代码、新建 / 删除文件 |
| 暂存区 | 快递打包区(准备提交的代码) | git add把工作区文件放到这里 |
| 本地仓库 | 自家仓库(已打包,有唯一编号) | git commit把暂存区文件存到这里 |
| 远程仓库 | 快递公司仓库(如 GitHub/Gitee) | git push/pull和本地仓库同步 |
核心逻辑:工作区 → 暂存区 → 本地仓库 → 远程仓库(开发流程);远程仓库 → 本地仓库 → 工作区(拉取代码流程)。
3. 提交(Commit):Git 的'最小版本单位'
- 每个提交就是一次代码快照,有唯一的哈希 ID(如
a8f57c),包含:修改内容、提交人、时间、提交信息; - 提交信息必须'有意义',比如'修复登录功能的空指针问题',而非'改了代码'。
基础款写作口诀(记下来):
「动词 + 范围 + 具体内容」
- 动词:实现 / 修复 / 新增 / 删除 / 优化 / 注释(精准描述动作);
- 范围:文件 / 函数 / 模块(比如 tree.c、前序遍历函数、登录模块);
- 具体内容:改了什么、解决了什么问题。
进阶款:行业通用规范(Conventional Commits)
如果团队有统一协作规范,可套用这个格式:
<类型>: <描述> // 空一行(可选) <详细说明>(可选) // 空一行(可选) <关联信息>(可选,比如 Fixes #123,关联 issue 编号)
第一步:先记 5 个常用'类型'(贴合 C 语言场景)
| 类型 | 含义 | 适用场景举例 |
|---|---|---|
| feat | 新增功能 | feat: 新增二叉树层次遍历函数 |
| fix | 修复 bug | fix: 修复 tree.c 中 findNode 函数的数组越界问题 |
| docs | 仅修改文档 / 注释 | docs: 给二叉树结构体添加字段说明注释 |
| style | 仅格式调整(不影响逻辑) | style: 调整 tree.c 的代码缩进,统一 4 空格 |
| refactor | 重构代码(不新增功能、不修复 bug) | refactor: 重构前序遍历函数,简化递归逻辑 |
第二步:描述部分'简洁 + 精准'(不超过 50 个字)
- 开头小写(除了专有名词);
- 不用句号结尾;
- 只说'做了什么',不说'我做了什么'(比如不说'我修复了 bug',直接说'修复 bug')。
第三步:详细说明(可选,复杂修改时加)
如果修改涉及多步逻辑、多个文件,可补充详细说明(比如'为什么这么改''改了哪些文件')。
完整例子:
fix: 修复二叉树销毁函数的内存泄漏问题
1. 原代码中仅 free 根节点,未递归 free 左右子节点;
2. 修改 destroyTree 函数,先递归销毁左右子树,再 free 根节点;
3. 新增空指针判断,避免段错误。
Fixes #5(项目 issue 编号,可选)
4. HEAD 指针:'当前在哪'的标记
- HEAD 是一个特殊指针,永远指向'你当前正在操作的版本 / 分支';
- 比如你在
main分支,HEAD 就指向main分支的最新提交;回退版本时,HEAD 会跟着指向旧提交。
三、Git 环境配置:从安装到初始化
1. 安装
- Windows:官网下载 Git 安装包(https://git-scm.com/),一路下一步,记住勾选'Git Bash Here'(右键能打开命令行);
- Linux:终端输
sudo apt install git(Ubuntu)/sudo yum install git(CentOS); - Mac:终端输
brew install git(需先装 Homebrew)。
验证安装:打开终端 / Git Bash,输 git --version,显示版本号就是装好了。
2. 基础配置(必须做!否则无法提交)
Git 需要知道'你是谁',才能记录提交人:
# 全局配置(所有仓库生效)
git config --global user.name "你的名字(如 ZhangSan)"
git config --global user.email "你的邮箱(如 [email protected])"
# 查看配置是否正确
git config --list
- 可选优化:配置默认编辑器
# 用 VSCode 作为编辑器(需先装 VSCode 并配置环境变量)
git config --global core.editor "code --wait"
# 用记事本(Windows)
git config --global core.editor "notepad++ -multiInst -notabbar -nosession -noPlugin"
3. 初始化仓库(两种方式)
方式 1:本地新建仓库(从零开始)
# 1. 新建文件夹(比如 lab-project),进入文件夹
mkdir lab-project && cd lab-project
# 2. 初始化 Git 仓库(生成 .git 隐藏文件夹,核心文件都在这)
git init
- 注意:
.git文件夹不要删!删了就等于删了整个版本记录。
方式 2:克隆远程仓库(已有项目)
# 克隆仓库到本地(HTTPS 方式,不用配密钥,适合新手)
git clone https://[账号]/[组织]/项目.git
# 克隆后自动生成文件夹,进入文件夹
cd 项目
- 补充:SSH 方式(免密推送,后续讲),先记住 HTTPS 足够用。
四、Git 本地核心操作:工作区→暂存区→本地仓库
1. 查看状态:git status(最常用命令之一)
- 每次操作后都可以输这个命令,看文件处于什么状态;
- 常见状态说明:
- 红色文字:文件在工作区(未暂存);
- 绿色文字:文件在暂存区(已暂存,待提交);
- 提示'working tree clean':工作区和暂存区一致,无未提交修改。
2. 暂存文件:git add(把代码放进'打包区')
# 暂存单个文件
git add main.c
# 暂存所有修改的文件(常用)
git add .
# 暂存指定目录下的所有文件
git add src/
# 交互式暂存(选部分代码提交,进阶但实用)
git add -p
- 新手注意:
git add后文件只是进了暂存区,还没保存到仓库!
3. 撤销暂存:加错文件怎么办?
# 撤销单个文件的暂存(回到工作区)
git reset HEAD main.c
# 撤销所有暂存的文件
git reset HEAD .
4. 提交修改:git commit(把打包的代码存进本地仓库)
# 基础提交(必须写提交信息)
git commit -m "feat: 新增二叉树前序遍历函数"
# 提交信息规范
# 类型:描述(类型:feat=新增功能,fix=修复 bug,docs=改文档,style=格式调整)
- 补充操作:
# 修改最后一次提交的信息(还没推远程时用)
git commit --amend
# 把暂存区文件合并到最后一次提交(避免重复提交)
git commit --amend --no-edit
5. 查看提交历史:git log(看'时光机'记录)
# 完整日志(显示哈希 ID、提交人、时间、信息)
git log
# 简洁日志(一行显示,适合看整体)
git log --oneline
# 图形化日志(带分支关系,后续讲分支时用)
git log --graph --oneline --all
# 显示每次提交的修改内容(看改了哪些代码)
git log -p
6. 版本回退:回到过去(核心!解决'代码改崩了')
第一步:找到要回退的版本 ID
输 git log --oneline,会看到类似这样的记录:
a8f57c (HEAD -> main) fix: 修复遍历空指针问题
7b92d1 feat: 新增中序遍历
5c68e1 init: 初始化项目,添加二叉树结构体
- 每个版本的前 7 位字符就是 ID(如
5c68e1)。
第二步:执行回退(三种模式)
| 命令 | 效果(通俗版) | 适用场景 |
|---|---|---|
git reset --hard ID | 工作区 + 暂存区 + 仓库全回退,彻底回到旧版本 | 代码完全改崩,想从头再来 |
git reset --mixed ID | 仓库 + 暂存区回退,工作区保留修改 | 想保留工作区代码,重新提交 |
git reset --soft ID | 仅仓库回退,暂存区 + 工作区都保留 | 只是提交信息写错,想重提交 |
- 示例:回退到初始化版本
git reset --hard 5c68e1
第三步:恢复误回退的版本
如果回退后后悔了,用 git reflog(记录所有操作记录):
# 查看所有操作记录(包括回退、提交、切换分支)
git reflog
# 找到回退前的版本 ID,再切回去
git reset --hard a8f57c
7. 忽略文件:.gitignore(避免提交无用文件)
常犯的错误:把编译后的 .exe、日志文件、临时文件提交到仓库,导致仓库臃肿。
第一步:新建 .gitignore 文件
在仓库根目录新建文件,命名为 .gitignore(注意前面有个点)。
第二步:编写忽略规则(通俗示例)
# 忽略所有 C 语言编译后的可执行文件
*.exe
*.out
# 忽略日志文件
*.log
# 忽略指定文件夹(比如存放测试数据的 data 文件夹)
data/
# 忽略指定文件(比如配置文件 config.h)
config.h
# 例外:虽然忽略*.log,但保留 test.log
!test.log
第三步:生效规则(已提交的文件需额外操作)
- 如果文件还没提交:直接保存
.gitignore,git add .即可; - 如果文件已经提交过:先从仓库删除,再忽略
git rm --cached main.exe # 从仓库删除,保留本地文件
git add .gitignore
git commit -m "docs: 添加.gitignore,忽略可执行文件"
五、Git 分支:并行开发的核心
1. 分支是什么?(用'游戏存档'类比)
- 主分支(
main/master):正式版本的存档,永远保持可运行; - 功能分支(如
dev-login):新开一个存档,专门开发登录功能,不影响主存档; - 开发完功能后,把功能分支合并回主分支,就像'把新功能整合到正式存档'。
2. 分支核心命令(手把手教)
# 1. 查看分支
git branch # 查看本地分支(*号表示当前分支)
git branch -r # 查看远程分支
git branch -a # 查看所有分支(本地 + 远程)
# 2. 创建分支
git branch dev-tree # 创建 dev-tree 分支(仅创建,不切换)
# 3. 切换分支
git checkout dev-tree # 切换到 dev-tree 分支
# 4. 创建 + 切换(最常用)
git checkout -b dev-tree # 等价于上面两步
# 5. 删除分支(已合并到主分支)
git branch -d dev-tree
# 6. 强制删除(未合并,确认不要了再删)
git branch -D dev-tree
3. 分支合并:把功能整合到主分支(核心流程)
以'把 dev-tree 分支合并到 main 分支'为例:
# 第一步:切换到主分支(要合并到哪个分支,就切到哪个)
git checkout main
# 第二步:确保主分支是最新的(后续远程协作会讲)
git pull origin main
# 第三步:执行合并
git merge dev-tree
两种合并方式
- Fast-forward(快进):如果主分支没新修改,直接把主分支指针移到功能分支的最新提交(无冲突);
- No-fast-forward(推荐):创建新提交,保留分支历史(能看出来'这个功能是从 dev-tree 合并来的'):
git merge --no-ff -m "merge: 合并二叉树功能到 main" dev-tree
4. 冲突解决:多人协作必遇问题
冲突产生场景
两个分支修改了同一个文件的同一行代码,Git 不知道保留哪个,就会标红冲突。
冲突标识(打开文件能看到)
<<<<<<< HEAD // 当前分支(main)的代码
printf("二叉树前序遍历\n");
=======
// 待合并分支(dev-tree)的代码
printf("Binary Tree PreOrder\n");
>>>>>>> dev-tree // 分支名
解决步骤(三步搞定)
- 手动修改文件:保留需要的代码,删除冲突标识(
<<<<<<</=======/>>>>>>>);比如保留中文版本:printf("二叉树前序遍历\n"); - 暂存修改后的文件:
git add 冲突文件名; - 完成合并提交:
git commit -m "fix: 解决二叉树遍历文案冲突"。
六、Git 远程协作
1. 远程仓库基础操作
第一步:关联远程仓库(本地新建仓库需做)
# 关联远程仓库(origin 是远程仓库的别名,默认叫 origin)
git remote add origin https://[账号]/[组织]/项目.git
# 查看关联的远程仓库
git remote -v
第二步:推送本地代码到远程(第一次推送)
# -u:关联本地分支和远程分支(后续推送只需 git push)
git push -u origin main
第三步:后续推送(本地有新提交后)
git push origin main # 推 main 分支
git push origin dev-tree # 推功能分支
第四步:拉取远程最新代码(同事改了远程仓库,你要同步)
# 拉取并合并(等价于 git fetch + git merge,新手用这个)
git pull origin main
# 仅拉取不合并(进阶,先看修改再合并)
git fetch origin main
git merge origin/main
2. 多人协作标准流程
避免多人直接改主分支,规范流程:
- 克隆远程仓库到本地:
git clone 仓库地址; - 新建功能分支:
git checkout -b 你的功能分支(如dev-zhangsan-tree); - 本地开发、提交:
git add .→git commit -m "xxx"; - 拉取主分支最新代码(避免冲突):
git pull origin main; - 推送自己的功能分支到远程:
git push -u origin 你的功能分支; - 提合并请求(PR/MR):在 Gitee/GitHub 上点'合并请求',让负责人审核;
- 审核通过后,负责人把你的分支合并到主分支;
- 本地删除功能分支:
git checkout main→git pull origin main→git branch -d 你的功能分支。
3. SSH 免密推送(进阶,解决每次输密码)
如果觉得每次推送输账号密码麻烦,可以配置 SSH:
- 生成 SSH 密钥:
ssh-keygen -t rsa -C "你的邮箱"(一路回车,默认路径); - 复制公钥:
- Windows:打开
C:\Users\你的用户名\.ssh\id_rsa.pub,复制里面的内容; - Linux/Mac:
cat ~/.ssh/id_rsa.pub,复制输出内容;
- Windows:打开
- 粘贴到 Gitee/GitHub 的'SSH 公钥'设置里;
- 测试:
ssh -T [email protected](Gitee)/ssh -T [email protected](GitHub),显示欢迎信息就是配好了; - 切换仓库地址为 SSH:
git remote set-url origin [email protected]:[账号]/[组织]/项目.git。
七、Git 进阶技巧
1. 暂存工作区:git stash(临时存代码)
场景:正在开发功能,突然要改 bug,不想提交未完成的代码:
# 暂存当前工作区(给暂存起个名字,方便识别)
git stash save "未完成的二叉树后序遍历"
# 查看暂存列表
git stash list
# 恢复暂存(并删除暂存记录)
git stash pop
# 恢复指定暂存
git stash apply stash@{0}
# 删除暂存记录
git stash drop stash@{0}
2. 标签:标记重要版本(如发布版)
# 创建轻量标签(仅标记)
git tag v1.0.0
# 创建带注释的标签(推荐)
git tag -a v1.0.0 -m "第一个版本:实现二叉树基本遍历"
# 推送标签到远程
git push origin v1.0.0
# 查看所有标签
git tag
3. 变基:git rebase(整理提交历史)
场景:提交记录太乱,想合并成整洁的记录(替代 merge,避免分支分叉):
# 把 dev-tree 分支的提交,变基到 main 分支最新版本
git checkout dev-tree
git rebase main
# 解决变基冲突(和 merge 冲突解决一样),解决后输:
git add .
git rebase --continue
# 变基完成后,合并到 main 分支
git checkout main
git merge dev-tree
八、Git 常见坑与避坑指南
- 不要直接改 main 分支:永远在功能分支开发,合并前先审核;
- 提交信息不要乱写:用'类型:描述'的格式,方便后续追溯;
- 删除.git 文件夹 = 删版本记录:千万不要删!
- 回退远程已推送的版本:不要用
git reset --hard,改用git revert(创建新提交撤销旧提交); - 冲突只改自己懂的代码:不要乱删同事的代码,不确定就问;
- 定期拉取远程代码:避免本地代码太旧,合并时产生大量冲突。
总结:Git 核心工作流程
克隆/初始化仓库 → 新建分支开发 → git add 暂存 → git commit 提交 → git pull 拉取最新 → git push 推送 → 提合并请求 → 合并到主分支

