面向前端团队的 Git 实战博客:覆盖初始化、回滚、忽略规则、分支协作、远程仓库与 GitFlow。理论参考 Pro Git、Git 官方文档、GitHub Docs。
目录
- [0. 知识脉络与名词百科](#0. 知识脉络与名词百科)
- [0.1 本章知识图谱](#0.1 本章知识图谱)
- [0.2 核心名词解析](#0.2 核心名词解析)
- [1. Git 基础回顾](#1. Git 基础回顾)
- [1.1 核心概念解析](#1.1 核心概念解析)
- [1.3 基础命令速查 · Git 配置三层优先级](#1.3 基础命令速查 · Git 配置三层优先级)
- [1.4 差异查看与撤销操作](#1.4 差异查看与撤销操作)
- [1.5 Git 内部对象模型](#1.5 Git 内部对象模型)
- [2. 版本管理与回滚](#2. 版本管理与回滚)
- [2.1 查看历史版本 · git log -S 代码考古](#2.1 查看历史版本 · git log -S 代码考古)
- [2.2 版本回滚操作](#2.2 版本回滚操作)
- [2.3 reset 三种模式](#2.3 reset 三种模式)
- [2.4 git revert:安全撤销](#2.4 git revert:安全撤销)
- [2.5 标签管理](#2.5 标签管理)
- [3. Git 忽略文件机制](#3. Git 忽略文件机制)
- [4. Git 分支管理策略](#4. Git 分支管理策略)
- [4.6 git rebase:变基操作](#4.6 git rebase:变基操作)
- [4.7 git cherry-pick:精准摘取](#4.7 git cherry-pick:精准摘取)
- [4.8 Detached HEAD 状态解析](#4.8 Detached HEAD 状态解析)
- [5. 远程仓库与协作开发](#5. 远程仓库与协作开发)
- [5.3 git fetch vs git pull(重要)](#5.3 git fetch vs git pull(重要))
- [5.4 场景化操作流程](#5.4 场景化操作流程)
- [5.7 SSH 密钥免密登录](#5.7 SSH 密钥免密登录)
- [6. 企业级 Git 工作流](#6. 企业级 Git 工作流)
- [6.1 GitFlow](#6.1 GitFlow)
- [6.2 GitHub Flow](#6.2 GitHub Flow)
- [6.3 GitLab Flow](#6.3 GitLab Flow)
- [6.4 Trunk-Based Development(新增)](#6.4 Trunk-Based Development(新增))
- [7. Git 高级技巧与最佳实践](#7. Git 高级技巧与最佳实践)
- [7.1 高级命令 · git stash 进阶](#7.1 高级命令 · git stash 进阶)
- [7.2 Git Hooks 配置](#7.2 Git Hooks 配置)
- [7.3 git bisect · git worktree](#7.3 git bisect · git worktree)
- [7.4 git blame:代码考古(新增)](#7.4 git blame:代码考古(新增))
- [7.5 git submodule:子模块(新增)](#7.5 git submodule:子模块(新增))
- [7.6 团队协作最佳实践](#7.6 团队协作最佳实践)
- [8. 故障排除与问题解决](#8. 故障排除与问题解决)
- [9. 总结与展望](#9. 总结与展望)
- [附录 A:命令与场景速查(扩充)](#附录 A:命令与场景速查(扩充))
- [附录 B:官方与延伸阅读](#附录 B:官方与延伸阅读)
0. 知识脉络与名词百科
0.1 本章知识图谱
#mermaid-svg-K4hWQU7FWCzbkLb7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K4hWQU7FWCzbkLb7 .error-icon{fill:#552222;}#mermaid-svg-K4hWQU7FWCzbkLb7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K4hWQU7FWCzbkLb7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K4hWQU7FWCzbkLb7 .marker.cross{stroke:#333333;}#mermaid-svg-K4hWQU7FWCzbkLb7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K4hWQU7FWCzbkLb7 p{margin:0;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge{stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 text{fill:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth--1{stroke-width:17;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-0{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-0{stroke-width:14;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-1{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-1{stroke-width:11;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 text{fill:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-2{stroke-width:8;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-3{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-3{stroke-width:5;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-4{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-4{stroke-width:2;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-5{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-5{stroke-width:-1;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-6{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-6{stroke-width:-4;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-7{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-7{stroke-width:-7;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-8{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-8{stroke-width:-10;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-9{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-9{stroke-width:-13;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 polygon,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 text{fill:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .node-icon-10{font-size:40px;color:black;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge-depth-10{stroke-width:-16;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:lightgray;}#mermaid-svg-K4hWQU7FWCzbkLb7 .disabled text{fill:#efefef;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root rect,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root path,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root circle,#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root text{fill:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-root span{color:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .section-2 span{color:#ffffff;}#mermaid-svg-K4hWQU7FWCzbkLb7 .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-K4hWQU7FWCzbkLb7 .edge{fill:none;}#mermaid-svg-K4hWQU7FWCzbkLb7 .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-K4hWQU7FWCzbkLb7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Git 完全指南
基础
init add commit status
diff restore
config 三层优先级
版本
log reflog
log -S Pickaxe 考古
reset soft mixed hard
revert 安全撤销
tag 语义化版本
忽略
gitignore 语法
rm --cached
check-ignore 调试
分支
branch switch merge
rebase 变基
cherry-pick 摘取
Detached HEAD
冲突解决
远程
fetch vs pull 区别
remote push clone
SSH 免密
submodule 子模块
工作流
GitFlow
GitHub Flow
GitLab Flow
Trunk-Based Dev
高级
stash 储藏
bisect 二分调试
worktree 多工作区
blame 代码考古
Hooks 质量门禁
0.2 核心名词解析
| 名词 | 英文 | 解析 |
|---|---|---|
| Commit | 提交 | 将暂存区快照写入本地对象库,生成唯一 SHA-1 哈希 |
| HEAD | HEAD | 当前检出分支/提交的指针 |
| Index | 暂存区 | git add 后的中间层,介于工作区与版本库之间 |
| Reflog | 引用日志 | 记录 HEAD 移动历史,误操作恢复的「黑匣子」 |
| Fast-forward | 快进合并 | 目标分支直接移动到源分支顶端,无分叉 |
| Upstream | 上游分支 | git push -u 建立的本地与远程跟踪关系 |
| Origin | 默认远程名 | git clone 自动创建的远程别名 |
| Pull Request | 合并请求 | GitHub/GitLab 上请求将功能分支并入主线的协作方式 |
| Blob | 对象 | Git 存储文件内容的最小单元,不含文件名 |
| Tree | 树对象 | 记录目录结构与 Blob 对应关系的对象 |
| Tag | 标签 | 指向某次提交的永久性引用,常用于版本发布 |
| Stash | 储藏 | 临时保存未完成的工作区改动,方便切换任务 |
| Rebase | 变基 | 将提交序列移植到另一个基点,产生线性历史 |
| Cherry-pick | 摘取 | 将指定提交的改动应用到当前分支 |
| Detached HEAD | 游离 HEAD | HEAD 直接指向某个提交而非分支时的状态 |
1. Git基础回顾
Git是目前世界上最先进的分布式版本控制系统,由Linux之父Linus Torvalds于2005年创建。它不仅用于代码管理,还广泛应用于各种需要版本控制的领域。
1.1 核心概念解析
【名词解释】
- 版本控制系统 (VCS):记录文件内容变化,以便将来查阅特定版本修订情况的系统
- 分布式版本控制:每个客户端都拥有完整的代码仓库副本,可以离线工作
- 工作区:当前看到的文件内容
- 暂存区:准备下次提交的文件索引
- 版本库:Git保存项目元数据和对象数据库的地方
1.2 Git工作流程图
#mermaid-svg-NH6NJ0b31oSZo1BN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NH6NJ0b31oSZo1BN .error-icon{fill:#552222;}#mermaid-svg-NH6NJ0b31oSZo1BN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NH6NJ0b31oSZo1BN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NH6NJ0b31oSZo1BN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NH6NJ0b31oSZo1BN .marker.cross{stroke:#333333;}#mermaid-svg-NH6NJ0b31oSZo1BN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NH6NJ0b31oSZo1BN p{margin:0;}#mermaid-svg-NH6NJ0b31oSZo1BN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster-label text{fill:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster-label span{color:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster-label span p{background-color:transparent;}#mermaid-svg-NH6NJ0b31oSZo1BN .label text,#mermaid-svg-NH6NJ0b31oSZo1BN span{fill:#333;color:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN .node rect,#mermaid-svg-NH6NJ0b31oSZo1BN .node circle,#mermaid-svg-NH6NJ0b31oSZo1BN .node ellipse,#mermaid-svg-NH6NJ0b31oSZo1BN .node polygon,#mermaid-svg-NH6NJ0b31oSZo1BN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NH6NJ0b31oSZo1BN .rough-node .label text,#mermaid-svg-NH6NJ0b31oSZo1BN .node .label text,#mermaid-svg-NH6NJ0b31oSZo1BN .image-shape .label,#mermaid-svg-NH6NJ0b31oSZo1BN .icon-shape .label{text-anchor:middle;}#mermaid-svg-NH6NJ0b31oSZo1BN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NH6NJ0b31oSZo1BN .rough-node .label,#mermaid-svg-NH6NJ0b31oSZo1BN .node .label,#mermaid-svg-NH6NJ0b31oSZo1BN .image-shape .label,#mermaid-svg-NH6NJ0b31oSZo1BN .icon-shape .label{text-align:center;}#mermaid-svg-NH6NJ0b31oSZo1BN .node.clickable{cursor:pointer;}#mermaid-svg-NH6NJ0b31oSZo1BN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NH6NJ0b31oSZo1BN .arrowheadPath{fill:#333333;}#mermaid-svg-NH6NJ0b31oSZo1BN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NH6NJ0b31oSZo1BN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NH6NJ0b31oSZo1BN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NH6NJ0b31oSZo1BN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NH6NJ0b31oSZo1BN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NH6NJ0b31oSZo1BN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster text{fill:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN .cluster span{color:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NH6NJ0b31oSZo1BN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NH6NJ0b31oSZo1BN rect.text{fill:none;stroke-width:0;}#mermaid-svg-NH6NJ0b31oSZo1BN .icon-shape,#mermaid-svg-NH6NJ0b31oSZo1BN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NH6NJ0b31oSZo1BN .icon-shape p,#mermaid-svg-NH6NJ0b31oSZo1BN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NH6NJ0b31oSZo1BN .icon-shape .label rect,#mermaid-svg-NH6NJ0b31oSZo1BN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NH6NJ0b31oSZo1BN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NH6NJ0b31oSZo1BN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NH6NJ0b31oSZo1BN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} git add
git commit
git push
git pull/clone
git checkout/reset
git restore --staged
工作区
暂存区
本地版本库
远程仓库
1.3 基础命令速查
bash
# ── 用户信息配置(每台机器首次使用 Git 时执行一次即可)──
# --global:写入 ~/.gitconfig,对本机所有仓库生效;单仓库可用 --local 覆盖
git config --global user.name '开发者用户名' # 显示在 git log / GitHub 贡献图
git config --global user.email 'user@example.com' # 须与托管平台账号邮箱一致,否则贡献不计入
# ── 仓库生命周期:在工作区 → 暂存区 → 本地版本库 之间移动文件状态 ──
git init # 当前目录生成 .git/,建立空仓库;已有仓库勿重复 init
git status # 列出未跟踪/已修改/已暂存文件;操作前先看,避免误提交
git add <file> # 将指定文件快照写入暂存区(Index),可多次 add 再一次性 commit
git add -A # 暂存工作区全部变更(含删除);等价于 git add --all
git commit -m '描述信息' # 将暂存区内容固化为不可变提交对象,生成 40 位 SHA-1 哈希
【代码注释】 :git config --global 写入 ~/.gitconfig,对本机所有仓库生效;user.name / user.email 会写入每次 commit 的 author 字段,须与 GitHub 账号邮箱一致贡献图才统计。git init 仅在空目录或新项目执行一次;git status 是操作前「仪表盘」;git add 只影响暂存区,未 add 的改动不会进入 commit;git commit 生成不可变快照,message 建议用约定式提交(如 feat:、fix:)。
Git 配置三层优先级(理论精讲)
Git 配置分三个作用域,优先级由高到低:
| 层级 | 存储位置 | 作用范围 | 命令参数 |
|---|---|---|---|
| local(仓库级) | .git/config |
仅当前仓库 | git config --local / 默认 |
| global(用户级) | ~/.gitconfig |
本机所有仓库 | git config --global |
| system(系统级) | /etc/gitconfig |
机器上所有用户 | git config --system(需 root) |
bash
# ── 配置层级查看与管理 ──
# 查看某个配置的最终生效值(以及来自哪个文件)
git config --show-origin user.email
# 查看某层的全部配置(三选一)
git config --global --list
git config --local --list
git config --system --list
# 典型场景:公司机器全局用公司邮箱,开源项目单独用个人邮箱
cd ~/open-source/my-lib
git config --local user.email "personal@example.com" # 只影响该仓库
git config --local user.name "个人昵称"
# 常用全局配置(开箱即用推荐组合)
git config --global core.autocrlf input # macOS/Linux:提交时统一 LF
git config --global pull.rebase true # git pull 默认以 rebase 代替 merge
git config --global init.defaultBranch main # 新仓库默认主分支名用 main
git config --global core.editor "code --wait" # 用 VS Code 作为提交说明编辑器
git config --global merge.conflictstyle diff3 # 冲突标记显示三方(含共同祖先),更易判断
# 查看当前生效的完整配置(含继承)
git config --list --show-origin
【代码注释】 :local 覆盖 global 覆盖 system------每层配置文件都是 INI 格式,可直接用文本编辑器修改。pull.rebase true 使 git pull 默认行为变为 fetch + rebase,保持线性历史、减少「merge bubble」。core.autocrlf=input 确保仓库中统一存储 LF,避免 Windows/Mac 混团队时的换行符噪音。merge.conflictstyle diff3 在冲突标记中额外显示公共祖先版本,让判断该保留哪侧逻辑更直观。
1.4 差异查看与撤销操作
在 add 与 commit 之间,常用 diff 查看改动、用 restore 撤销误操作(Git 2.23+ 推荐,语义比 checkout 更清晰)。
bash
# ── 差异对比:弄清「改在哪一层」再决定撤销策略 ──
# 对比:工作区 vs 暂存区(有修改但尚未 git add 时才有输出)
git diff
# 对比:暂存区 vs 最后一次 commit(已 add、未 commit;Code Review 提交前必看)
git diff --cached
# 同义写法:git diff --staged
# ── 撤销工作区改动(仅影响 Working Tree,暂存区与历史不变)──
git restore 文件名 # 单文件:丢弃未 add 的修改,恢复为暂存区/HEAD 版本
git restore . # 批量:当前目录下所有已跟踪文件的未暂存改动一并丢弃
# ── 撤销暂存(把文件从 Index 撤出,工作区编辑内容保留)──
git restore --staged 文件名 # 单文件:取消暂存,可重新 git add -p 挑选块
git restore --staged . # 全部撤出暂存区,相当于「误点 add .」后的急救
# ── 快捷提交:对已跟踪文件的「修改/删除」一步完成 add + commit ──
# 注意:新建文件(Untracked)不在此列,必须先 git add
git commit -am 'fix: update tracked files only'
【代码注释】 :git diff 无参数时比的是工作区 ↔ 暂存区 ;--cached 比的是暂存区 ↔ 最新 commit ,提交前用它做最后一遍自查。git restore(Git 2.23+)只撤销未提交状态,不能 找回已 commit 的内容,历史回退用 reset / revert。-am 中的 -a 自动暂存已跟踪 文件的修改与删除,新建 文件仍须先 git add;-m 紧跟提交说明。误用 git restore . 会永久丢失未 add 的编辑,操作前可用 git stash 备份。
经典应用场景:
- 误改配置 :
git restore .env丢弃工作区未提交改动 - 误暂存 :
git restore --staged .全部撤出暂存区再挑选git add -p - Code Review 前 :
git diff --cached确认即将提交的内容
可运行示例 · 本地 Git 练习页 (在空目录保存为 git-practice.html,按步骤在终端执行对应命令):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Git 基础练习清单</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 2rem auto; line-height: 1.6; }
code { background: #f4f4f5; padding: 2px 6px; border-radius: 4px; }
pre { background: #18181b; color: #e4e4e7; padding: 1rem; border-radius: 8px; overflow-x: auto; }
h2 { border-bottom: 1px solid #e4e4e7; padding-bottom: 0.25rem; }
</style>
</head>
<body>
<h1>Git 基础命令练习</h1>
<p>在终端进入空文件夹,按顺序执行下列命令,观察每步 <code>git status</code> 变化。</p>
<h2>步骤 1:初始化</h2>
<pre>git init
echo "hello" > readme.txt
git status</pre>
<h2>步骤 2:暂存与提交</h2>
<pre>git add readme.txt
git commit -m "docs: add readme"</pre>
<h2>步骤 3:修改与 diff</h2>
<pre>echo "world" >> readme.txt
git diff
git add readme.txt
git diff --cached</pre>
<h2>步骤 4:撤销</h2>
<pre>echo "mistake" >> readme.txt
git restore readme.txt
git status</pre>
</body>
</html>
【代码注释】 :HTML 页内 <pre> 仅为命令清单与步骤导航 ,不执行 Git;须在系统终端进入空目录按序操作。每步后执行 git status 观察「未跟踪 / 已修改 / 已暂存」状态变化,建立工作区→暂存区→版本库的肌肉记忆。
1.5 Git 内部对象模型(理论精讲)
理解 Git 的存储机制,才能真正读懂每条命令背后发生了什么。
Git 本质是一个内容寻址文件系统 :每次你 git add 一个文件,Git 就把文件内容压缩后用 SHA-1 哈希命名,存入 .git/objects/。
四种核心对象
.git/objects/
├── blob ← 存文件内容(无文件名)
├── tree ← 存目录结构(文件名 → blob 哈希)
├── commit ← 存快照元信息(tree + parent + message)
└── tag ← 存附注标签(指向 commit)
#mermaid-svg-j3ozYPiivi5eCWqs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-j3ozYPiivi5eCWqs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-j3ozYPiivi5eCWqs .error-icon{fill:#552222;}#mermaid-svg-j3ozYPiivi5eCWqs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-j3ozYPiivi5eCWqs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-j3ozYPiivi5eCWqs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-j3ozYPiivi5eCWqs .marker.cross{stroke:#333333;}#mermaid-svg-j3ozYPiivi5eCWqs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-j3ozYPiivi5eCWqs p{margin:0;}#mermaid-svg-j3ozYPiivi5eCWqs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-j3ozYPiivi5eCWqs .cluster-label text{fill:#333;}#mermaid-svg-j3ozYPiivi5eCWqs .cluster-label span{color:#333;}#mermaid-svg-j3ozYPiivi5eCWqs .cluster-label span p{background-color:transparent;}#mermaid-svg-j3ozYPiivi5eCWqs .label text,#mermaid-svg-j3ozYPiivi5eCWqs span{fill:#333;color:#333;}#mermaid-svg-j3ozYPiivi5eCWqs .node rect,#mermaid-svg-j3ozYPiivi5eCWqs .node circle,#mermaid-svg-j3ozYPiivi5eCWqs .node ellipse,#mermaid-svg-j3ozYPiivi5eCWqs .node polygon,#mermaid-svg-j3ozYPiivi5eCWqs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-j3ozYPiivi5eCWqs .rough-node .label text,#mermaid-svg-j3ozYPiivi5eCWqs .node .label text,#mermaid-svg-j3ozYPiivi5eCWqs .image-shape .label,#mermaid-svg-j3ozYPiivi5eCWqs .icon-shape .label{text-anchor:middle;}#mermaid-svg-j3ozYPiivi5eCWqs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-j3ozYPiivi5eCWqs .rough-node .label,#mermaid-svg-j3ozYPiivi5eCWqs .node .label,#mermaid-svg-j3ozYPiivi5eCWqs .image-shape .label,#mermaid-svg-j3ozYPiivi5eCWqs .icon-shape .label{text-align:center;}#mermaid-svg-j3ozYPiivi5eCWqs .node.clickable{cursor:pointer;}#mermaid-svg-j3ozYPiivi5eCWqs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-j3ozYPiivi5eCWqs .arrowheadPath{fill:#333333;}#mermaid-svg-j3ozYPiivi5eCWqs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-j3ozYPiivi5eCWqs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-j3ozYPiivi5eCWqs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j3ozYPiivi5eCWqs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-j3ozYPiivi5eCWqs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j3ozYPiivi5eCWqs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-j3ozYPiivi5eCWqs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-j3ozYPiivi5eCWqs .cluster text{fill:#333;}#mermaid-svg-j3ozYPiivi5eCWqs .cluster span{color:#333;}#mermaid-svg-j3ozYPiivi5eCWqs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-j3ozYPiivi5eCWqs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-j3ozYPiivi5eCWqs rect.text{fill:none;stroke-width:0;}#mermaid-svg-j3ozYPiivi5eCWqs .icon-shape,#mermaid-svg-j3ozYPiivi5eCWqs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-j3ozYPiivi5eCWqs .icon-shape p,#mermaid-svg-j3ozYPiivi5eCWqs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-j3ozYPiivi5eCWqs .icon-shape .label rect,#mermaid-svg-j3ozYPiivi5eCWqs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-j3ozYPiivi5eCWqs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-j3ozYPiivi5eCWqs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-j3ozYPiivi5eCWqs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} commit v2
tree: t2
parent: C1
tree t2
README → blob_r2
src/ → tree_s
commit v1
tree: t1
blob: README 内容 v2
tree src/
index.js → blob_i
blob: index.js 内容
关键推论:
- 同一内容的两个文件只保存一个 blob(内容相同 → 哈希相同)
- 每次
commit只存变化的部分,未改动文件复用旧 blob------Git 天然节省空间 - SHA-1 哈希由内容决定,数据损坏时哈希不匹配即可发现
bash
# ── 亲手观察 Git 对象库(在任意已有 .git 的仓库根目录执行)──
git cat-file -t HEAD # -t:打印对象类型,HEAD 通常为 commit
git cat-file -p HEAD # -p:pretty-print,可见 tree 哈希、parent、author、message
git cat-file -p HEAD^{tree} # ^{tree}:从 commit 解引用到根目录 tree 对象
git ls-tree HEAD # 以「模式 类型 哈希 路径」列出根目录条目,比 cat-file 更直观
# 按「提交:路径」语法直接查看某路径在 HEAD 处的 blob 原文(不经工作区)
git cat-file -p HEAD:src/index.js
【代码注释】 :Git 用 SHA-1 对内容 寻址:.git/objects/ab/cdef... 中前 2 位为子目录、后 38 位为文件名;相同内容只存一份 blob。HEAD 是当前检出位置的符号引用,通常指向分支名,分支再指向最新 commit。HEAD^{tree} 是「从 commit 跳到其快照根目录」的语法;HEAD:path 等价于该路径在 HEAD 提交中的 blob。松散对象过多时 git gc 会打包为 .pack 并 prune 悬空对象,减小仓库体积。
1.6 基础模块归纳
| 命令 | 作用阶段 | 记忆口诀 |
|---|---|---|
git status |
任意 | 先看状态再操作 |
git add |
工作区→暂存区 | 挑选要提交的改动 |
git commit |
暂存区→版本库 | 快照落盘 |
git diff |
对比 | 未 add 看工作区,已 add 看 --cached |
git restore |
撤销 | 工作区 / 暂存区分别恢复 |
git cat-file -p |
探索对象库 | 理解 Git 内部存储结构 |
2. 版本管理与回滚
版本管理是Git的核心功能,允许开发者在任何时候回到项目的任意状态。
2.1 查看历史版本
Git为每次提交生成唯一的哈希值(版本号),通过这些标识符可以精确追踪每一次变更。
bash
# ── 查看提交历史(只读,不改变仓库状态)──
git log # 完整日志:哈希、作者、日期、完整 message
git log -3 # -n:仅显示最近 3 条,适合快速定位
git log --oneline # 每行一条:短哈希 + 首行 subject,适合日常浏览
git log --graph --oneline --all # --graph 画分支线;--all 含所有本地/远程跟踪分支
# ── 引用日志:HEAD 与分支指针的移动轨迹(含 reset 后「消失」的提交)──
git reflog # 默认 reflog show HEAD;误操作恢复时优先查这里
【代码注释】 :git log 遍历当前分支 可达的 commit 链;--all 才展示所有分支拓扑,合并排查时配合 --graph。提交哈希全局唯一,日志里常只显示前 7 位。git reflog 记录的是引用移动 (checkout、merge、reset 等),不是完整项目史;默认约 90 天过期。reset --hard 后 log 里看不到的提交,往往仍能通过 git reflog 找到哈希,再 git reset --hard <hash> 救回。
实际应用场景:
bash
# 单文件演进史:--follow 跨 rename 追踪;--patch 附带每次 diff
git log --follow --patch -- file.txt
# 按作者筛选(支持正则,如 --author="Zhang|Li")
git log --author="张三"
# 时间窗口:--since/--until 支持 "2 weeks ago"、"2024-12-31" 等自然语言
git log --since="2024-01-01" --until="2024-12-31"
# 每次提交改了哪些文件、增删行数汇总(不展开具体 diff)
git log --stat
【代码注释】 :路径前的 -- 用于与选项分隔,避免文件名被当成参数。--follow 在检测到 rename 时会延续历史,适合追查「这个 bug 是哪次改文件引入的」。--patch 输出量大,可配合 -S"关键字" 查「哪次提交引入/删除了某字符串」。--stat 适合 Code Review 前看影响面;分支拓扑用 git log --graph --oneline --all --decorate。
git log 高级过滤:代码考古三件套
在大型项目中,「哪次提交引入了某个函数/删掉了某行代码」是最常见的调试场景,以下三种过滤器专门解决这类问题:
bash
# ── 1. Pickaxe 搜索(-S):精准定位「某字符串出现/消失」的提交 ──
# -S 会扫描每次 commit 的 diff,仅返回「该字符串出现次数发生变化」的提交
git log -S "calculateTotal" --oneline
# 典型用途:某函数从什么时候开始被使用,或何时被删除
git log -S "api_secret_key" --all --oneline
# 安全审计:任意分支历史中是否曾经存在敏感字符串(即使已删除也能找到)
git log -S "require('lodash')" --patch -- "*.js"
# 结合 --patch:不仅找到提交,同时展示引入/删除该行的完整 diff 上下文
# ── 2. Regex 搜索(-G):正则匹配 diff 内容 ──
# 与 -S 区别:-G 匹配「任意包含该正则的 diff 行」,而非「出现次数变化」
git log -G "function\s+get[A-Z]" --oneline # 找新增或删除了 getXxx 系列函数的提交
# ── 3. 提交说明搜索(--grep):在 message 中过滤 ──
git log --grep="fix.*login" --oneline --all # 找所有修复登录相关的提交(正则)
git log --grep="JIRA-1234" --oneline # 按 Issue 编号查找关联提交
git log --grep="feat" --grep="auth" --all-match # --all-match:message 同时含两词(默认是 OR)
# ── 综合场景:定位某接口何时被废弃 ──
git log -S "fetchUserProfile" --oneline --all
# 输出含两个提交:一条「新增」、一条「删除」,精准还原废弃时间线
【代码注释】 :-S(Pickaxe)是 Git 内置的语义级代码搜索,比 grep 历史日志更准确,因为它理解 diff 的「加/减」语义;-G 则更适合模糊匹配------两者处理正则时行为不同,优先用 -S 搜精确字符串。--all 跨所有分支搜索,安全审计必加。--grep 结合 --all-match 可实现多关键词 AND 逻辑查询。
2.2 版本回滚操作
版本回滚是Git最强大的功能之一,允许项目状态在时间线上自由移动。
bash
# ── 将当前分支指针移动到指定提交(--hard 会同步清空工作区与暂存区)──
git reset --hard a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 # 完整 40 位 SHA-1
git reset --hard a1b2c3d # 前 7 位在仓库内唯一即可定位
# ── 相对当前 HEAD 的回退语法(^ 与 ~ 可组合,如 HEAD~3^2)──
git reset --hard HEAD^ # 回退 1 个提交(^ 等价于 ~1)
git reset --hard HEAD^^ # 回退 2 个提交(连续两个父提交,仅线性历史常用)
git reset --hard HEAD~5 # 回退 5 个提交(沿第一父链)
【代码注释】 :git reset 移动的是当前分支指针 ,不是删除 commit 对象;--hard 同时把工作区、暂存区重置为目标提交树,未提交的修改会永久丢失 。仅适合未推送 的本地纠错;已推送共享分支应改用 git revert。回滚前可用 git branch backup 或 git stash 留后路;误操作后用 git reflog 查移动前的哈希再 reset --hard 恢复。
版本回滚策略:
#mermaid-svg-y7aUZyZfZUgeJdQj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-y7aUZyZfZUgeJdQj .error-icon{fill:#552222;}#mermaid-svg-y7aUZyZfZUgeJdQj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-y7aUZyZfZUgeJdQj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-y7aUZyZfZUgeJdQj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-y7aUZyZfZUgeJdQj .marker.cross{stroke:#333333;}#mermaid-svg-y7aUZyZfZUgeJdQj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-y7aUZyZfZUgeJdQj p{margin:0;}#mermaid-svg-y7aUZyZfZUgeJdQj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster-label text{fill:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster-label span{color:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster-label span p{background-color:transparent;}#mermaid-svg-y7aUZyZfZUgeJdQj .label text,#mermaid-svg-y7aUZyZfZUgeJdQj span{fill:#333;color:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj .node rect,#mermaid-svg-y7aUZyZfZUgeJdQj .node circle,#mermaid-svg-y7aUZyZfZUgeJdQj .node ellipse,#mermaid-svg-y7aUZyZfZUgeJdQj .node polygon,#mermaid-svg-y7aUZyZfZUgeJdQj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-y7aUZyZfZUgeJdQj .rough-node .label text,#mermaid-svg-y7aUZyZfZUgeJdQj .node .label text,#mermaid-svg-y7aUZyZfZUgeJdQj .image-shape .label,#mermaid-svg-y7aUZyZfZUgeJdQj .icon-shape .label{text-anchor:middle;}#mermaid-svg-y7aUZyZfZUgeJdQj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-y7aUZyZfZUgeJdQj .rough-node .label,#mermaid-svg-y7aUZyZfZUgeJdQj .node .label,#mermaid-svg-y7aUZyZfZUgeJdQj .image-shape .label,#mermaid-svg-y7aUZyZfZUgeJdQj .icon-shape .label{text-align:center;}#mermaid-svg-y7aUZyZfZUgeJdQj .node.clickable{cursor:pointer;}#mermaid-svg-y7aUZyZfZUgeJdQj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-y7aUZyZfZUgeJdQj .arrowheadPath{fill:#333333;}#mermaid-svg-y7aUZyZfZUgeJdQj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-y7aUZyZfZUgeJdQj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-y7aUZyZfZUgeJdQj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y7aUZyZfZUgeJdQj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-y7aUZyZfZUgeJdQj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y7aUZyZfZUgeJdQj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster text{fill:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj .cluster span{color:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-y7aUZyZfZUgeJdQj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-y7aUZyZfZUgeJdQj rect.text{fill:none;stroke-width:0;}#mermaid-svg-y7aUZyZfZUgeJdQj .icon-shape,#mermaid-svg-y7aUZyZfZUgeJdQj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-y7aUZyZfZUgeJdQj .icon-shape p,#mermaid-svg-y7aUZyZfZUgeJdQj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-y7aUZyZfZUgeJdQj .icon-shape .label rect,#mermaid-svg-y7aUZyZfZUgeJdQj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-y7aUZyZfZUgeJdQj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-y7aUZyZfZUgeJdQj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-y7aUZyZfZUgeJdQj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
版本回滚决策
是否需要保留当前修改
使用 --soft 或 --mixed
使用 --hard
git reset --soft HEAD^
git reset --mixed HEAD^
git reset --hard HEAD^
保留工作区和暂存区修改
保留工作区修改,清空暂存区
完全回滚到指定版本
2.3 reset命令的三种模式
bash
# --soft:只回退 commit,暂存区与工作区保持「已 add 待提交」状态
git reset --soft HEAD~1 # 典型:改 commit message 或合并多次 commit
# --mixed(默认):回退 commit 并清空暂存区,工作区文件改动仍保留
git reset --mixed HEAD~1
git reset HEAD~1 # 省略 --mixed 时行为相同;改动回到「已修改未 add」
# --hard:三处全部对齐到目标提交,工作区未提交内容一并丢弃
git reset --hard HEAD~1 # 仅本地个人分支使用;共享分支禁止对已推送历史使用
实际应用示例:
bash
# 场景1:提交说明写错(尚未 push)------ soft 撤销提交,改动仍在暂存区
git commit -m "typo: fix buug"
git reset --soft HEAD~1 # HEAD 指向上一个提交,本次改动留在 Index
git commit -m "typo: fix bug" # 也可用 git commit --amend -m "..." 一步改说明
# 场景2:误把敏感文件或 node_modules 打进提交------ soft 后重新挑选暂存
git reset --soft HEAD~1
git restore --staged unwanted.txt # 从暂存区移除,文件仍留在磁盘
git commit -m "feat: add new feature"
# 场景3:本地实验失败,整仓回到远程稳定线(会丢失未提交改动!)
git fetch origin
git reset --hard origin/main # 或 reset 到本地 production 分支的 tip
【代码注释】 :三种模式差异在暂存区与工作区是否保留 :soft → 全保留;mixed(默认)→ 只保留工作区;hard → 全丢弃。改最后一次提交说明更推荐 git commit --amend(未 push 时)。reset --hard 后若 commit 已从当前分支 log 消失,对象仍在 reflog 中,用 git reflog → git reset --hard <旧哈希> 可恢复。
可运行示例 · 版本回滚练习:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Git log 与 reset 练习</title>
<style>
body { font-family: ui-monospace, monospace; max-width: 800px; margin: 2rem auto; }
pre { background: #0f172a; color: #cbd5e1; padding: 1rem; border-radius: 8px; }
.tip { background: #fef3c7; padding: 0.75rem; border-radius: 6px; margin: 1rem 0; }
</style>
</head>
<body>
<h1>log / reflog / reset 三步练</h1>
<div class="tip">先连续提交 3 次,再按下列命令观察 HEAD 变化。</div>
<pre>git log --oneline
git reset --hard HEAD^
git reflog
git reset --hard <reflog中的旧哈希></pre>
<p>前 7 位 Commit ID 即可定位版本,无需完整 40 位 SHA。</p>
</body>
</html>
【代码注释】 :reflog 一行形如 abc1234 HEAD@{1}: reset: moving to HEAD^,左侧为可检出哈希。reset --hard 后用 git reset --hard abc1234 回到 reflog 中 HEAD@{n} 对应状态。HEAD@{0} 为当前,@{1} 为上一步操作后位置。
2.4 git revert:安全撤销已推送的提交
reset --hard 会重写历史 ,在团队共享分支上极其危险。git revert 通过新增一个反向提交来撤销效果,历史线不变。
bash
# 撤销最近一次提交:新建一个「反向 diff」的 commit,历史线不断开
git revert HEAD # 默认打开编辑器填写 revert 说明;加 --no-edit 沿用默认文案
# 撤销历史中任意一次提交(中间提交也可,可能产生冲突需手工解决)
git revert abc1234
# 批量撤销最近 3 次:先不自动 commit,便于一次性检查合并结果
git revert --no-commit HEAD~3..HEAD # 范围不含左端点,含右端 HEAD
git commit -m "revert: roll back last 3 commits"
# 撤销合并提交:merge commit 有两个 parent,必须指定保留哪条主线
git revert -m 1 <merge-commit-hash> # -m 1 通常指被合并进去的目标分支(如 main)
【代码注释】 :revert 通过新增提交 抵消某次改动,不改写已有哈希,适合已 push 的共享分支 。与 reset 对比:reset 移动指针「抹掉」后续提交(仅本地未共享时安全);revert 保留完整审计轨迹。合并提交有两个 parent,-m 1 选第一 parent 作为「主线」基准生成反向补丁;选错会导致 revert 内容异常。若 revert 了合并提交后又想再次合并同一功能分支,需先 revert 掉那次 revert commit(「revert the revert」),否则 Git 认为改动已存在而跳过。
reset vs revert 决策矩阵
| 场景 | 推荐操作 | 理由 |
|---|---|---|
| 本地未推送的提交 | git reset |
无副作用,历史更干净 |
| 已推送到共享分支 | git revert |
不改写历史,团队安全 |
| 撤销某一个特定提交 | git revert <hash> |
精准,不影响之后的提交 |
| 回到某个历史状态 | git reset --hard <hash> |
仅在本地个人分支使用 |
经典场景:线上紧急回滚
bash
# 1. 定位问题提交(共享分支上禁止 reset --hard,须用 revert)
git log --oneline -5
# e4f5a6b feat: new checkout flow ← 引入 bug 的 commit
# c3d4e5f fix: typo in header ← revert 后仍保留(在问题提交之后)
# a1b2c3d release: v2.3.0
# 2. 生成「反向补丁」新提交,历史不断开,队友 pull 即可同步回滚
git revert e4f5a6b # 默认打开编辑器;加 --no-edit 用自动说明
# 若 revert 冲突:解决 → git add → git revert --continue
# 3. 推送 revert 提交,CI 通常监听 main 的 push 触发回滚部署
git push origin main
2.5 标签管理(Tag)
标签是指向某次提交的永久快照,通常用于标记版本发布节点(v1.0.0、v2.3.1 等)。
bash
# 【轻量标签】仅是指向某 commit 的固定指针,无 tagger/message 元数据
git tag v1.0.0 # 默认打在 HEAD 当前提交上
git tag v1.0.0 abc1234 # 为历史某次提交补打标签(如漏标 release)
# 【附注标签 -a】独立 Git 对象,含打标者、时间、说明;正式发布、CHANGELOG 溯源首选
git tag -a v1.0.0 -m "Release version 1.0.0: initial stable release"
git tag -a v1.0.1 abc1234 -m "Hotfix: fix critical payment bug"
# 列出与查看
git tag # 按字母序列出本地所有标签
git tag -l "v1.*" # -l 支持通配,如 v1.2.x 系列
git show v1.0.0 # 显示 tag 对象 + 对应 commit 与 diff 摘要
# 与远程同步(标签默认不随 git push 分支一起推送)
git push origin v1.0.0 # 推送单个标签
git push origin --tags # 推送全部本地尚未在远程的标签
# 删除(本地 / 远程需分别操作)
git tag -d v1.0.0 # 删除本地标签引用
git push origin --delete v1.0.0 # 或:git push origin :refs/tags/v1.0.0
# 检出标签快照(HEAD 脱离分支,见 4.8 Detached HEAD)
git switch --detach v1.0.0 # Git 2.23+ 推荐;旧写法 git checkout v1.0.0
【代码注释】 :轻量标签是 .git/refs/tags/<name> 里存的一行 commit 哈希;附注标签(-a)是完整对象,可 GPG 签名(-s)。git push 默认只推分支,标签需显式 git push origin <tag> 或 --tags。SemVer(v主.次.修订)便于 CI 解析版本;npm version patch/minor/major 可自动改 package.json 并打 tag。生产热修应在 tag 对应 commit 上打 v1.0.1 而非移动已有 tag(git tag -f 会改指向,破坏他人 clone)。
语义化版本(SemVer)规范:
v主版本.次版本.修订号
v2.1.3
│ │ └─ 修订号:向后兼容的 bug 修复
│ └─── 次版本:新增向后兼容的功能
└───── 主版本:不兼容的 API 变更
经典场景:前端项目版本发布流程
bash
# 发版前:确保 main 与远程一致,且 CI 已通过
git switch main
git pull origin main
# minor:1.1.0 → 1.2.0;会改 package.json、自动 commit + 打 tag v1.2.0
npm version minor # patch/minor/major 对应修订/次版本/主版本
# 手写流程:改 version → git commit → git tag -a v1.2.0 -m "..."
# 分支与标签一并推送(标签不会随普通 git push 自动上去)
git push origin main --tags
# GitHub Releases 页面选 v1.2.0 写 changelog;流水线监听 tag push 打生产包
2.6 版本管理模块归纳
| 模式 | 工作区 | 暂存区 | 提交历史 | 典型用途 |
|---|---|---|---|---|
--soft |
保留 | 保留 | 回退 | 改 commit message |
--mixed |
保留 | 清空 | 回退 | 重新挑选 add |
--hard |
清空 | 清空 | 回退 | 彻底回到某版本(仅本地) |
git revert |
新增反向提交 | --- | 不变 | 安全撤销已推送提交 |
git tag -a |
--- | --- | --- | 标记发布版本 |
3. Git忽略文件机制
.gitignore文件是Git项目的重要组成部分,它定义了哪些文件不应该被版本控制跟踪。
3.1 需要忽略的文件类型
**【专业建议】**以下文件类型应该被Git忽略:
-
系统自动生成的文件
- macOS:
.DS_Store,.AppleDouble,.LSOverride - Windows:
Thumbs.db,ehthumbs.db,Desktop.ini - Linux:
*~,.nfs*
- macOS:
-
编译和构建产物
- JavaScript:
node_modules/,dist/,build/,.next/ - CSS:
.sass-cache/ - 通用:
*.min.js,*.min.css
- JavaScript:
-
IDE和编辑器配置
- VSCode:
.vscode/ - WebStorm/IntelliJ:
.idea/ - Vim:
*.swp,*.swo,*.un~ - Emacs:
*~,\#*\#,.\#*
- VSCode:
-
环境配置和敏感信息
.env,.env.local,.env.*.localconfig/secrets.js,credentials.json
-
日志和临时文件
*.log,npm-debug.log*,yarn-debug.log**.tmp,*.temp,*.cache
3.2 .gitignore语法详解
bash
# .gitignore 语法速查(规则相对于「该 .gitignore 文件所在目录」解析)
# 1. 空行:仅作可读性分隔;行尾 \ 可续行或转义特殊字符
# 2. # 开头:注释;若需匹配字面量 # 文件名,用 \# 转义
# 3. ! 否定:在已被上层规则排除后,重新纳入版本控制(顺序敏感,须写在排除规则之后)
*.a # 忽略所有 .a 静态库
!lib.a # 例外:保留 lib.a(常用于「忽略 build/*.a 但保留 vendor/lib.a」)
# 4. 末尾 /:只匹配目录(及其下所有内容),不匹配同名文件
build/ # 忽略 build/ 目录;无尾斜杠的 build 可能同时匹配文件
# 5. 开头 /:锚定到当前 .gitignore 所在目录(常为仓库根)
/TODO # 仅忽略根目录 TODO,不忽略 src/TODO
# 6. **:跨多级目录通配(Git 2.13+ 对 ** 支持更完整)
doc/**/*.pdf # doc 任意子目录下的 .pdf
# 7. *:单层路径内任意字符(不含 /)
*.txt # 任意目录下的 .txt,但不匹配 a/b.txt 中的跨层需用 **
# 8. ?:精确一个任意字符
file?.txt # file1.txt、fileA.txt,不匹配 file10.txt
# 9. []:字符类或范围
file[0-9].txt # file0.txt ... file9.txt
file[a-z].txt # 单个小写字母一位
【代码注释】 :.gitignore 只影响未跟踪 文件是否被 git add 发现;已跟踪 文件须先 git rm --cached。规则从仓库根向下继承,子目录可放独立 .gitignore 追加或覆盖。git check-ignore -v <path> 显示命中规则行号,排查「为何仍被跟踪」必备。否定 ! 若父目录整条被忽略,子路径 !子/file 可能无效,需先 !父目录/ 再 !父目录/子/file。
名词解析 · 否定规则 ! :先被 * 排除的文件,可用 !filename 重新纳入;若父目录已被排除,子路径的 ! 可能无效。
3.3 .gitignore配置示例
bash
# 【通用 Web 前端 .gitignore 模板】复制到仓库根目录,按项目删减
# 依赖目录(应通过 lock 文件 + CI 安装,勿提交二进制树)
node_modules/ # npm/pnpm/yarn 安装产物,体积巨大
bower_components/ # 旧版 Bower 依赖
vendor/ # PHP/混合项目的第三方包
# 构建输出(可由 CI 从源码生成,提交会导致 diff 噪音与冲突)
dist/ # Vite/Webpack 默认输出
build/ # CRA 等默认目录
out/ # Next.js 静态导出等
*.min.js # 压缩产物(若未统一放在 dist/)
*.min.css
# 环境配置(含 API Key、数据库密码,泄露须轮换密钥)
.env # 所有环境变量入口
.env.local # 本地覆盖,最高敏感
.env.development.local
.env.test.local
.env.production.local
# 日志与调试产物
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# 编辑器/IDE(个人偏好,不应强加给队友)
.vscode/ # 除共享的 extensions.json 外通常忽略
.idea/
*.swp # Vim 交换文件
*.swo
*~
.DS_Store # macOS 目录元数据
# 测试覆盖率报告(CI 生成即可)
coverage/
.nyc_output/
# 临时与缓存
*.tmp
*.temp
.cache/ # 各类工具本地缓存
# Windows 系统文件
Thumbs.db
Desktop.ini
【代码注释】 :模板按类别分组,复制后删除与本项目无关的行即可。node_modules/、dist/、.env* 是前端仓库必忽略 三项;.vscode/ 若团队共享 settings.json 可改为只忽略 !.vscode/settings.json。提交前用 git status 确认无巨型目录处于 Untracked 再首次 push。
3.4 忽略已提交文件的正确流程
当文件已经被提交到版本库,需要从版本控制中移除但仍保留本地文件:
bash
# 1. 从 Git 索引(暂存区/版本库跟踪列表)移除,磁盘文件原样保留
git rm --cached filename # 之后该文件变为 Untracked,须配合 .gitignore
# 2. 递归移除目录下所有已跟踪项(如误提交的 node_modules)
git rm -r --cached directory/ # -r:递归;目录本身可仍在本地
# 3. 写入忽略规则,防止下次 git add . 再次纳入
echo "filename" >> .gitignore # 建议用编辑器追加并 review,避免重复规则
# 4. 提交「停止跟踪」这一变更(团队 pull 后对方工作区会删该文件,需事先沟通)
git add .gitignore
git commit -m "chore: stop tracking filename and update gitignore"
【代码注释】 :--cached 只动索引 ,不动工作区文件;提交后远程仓库不再包含该路径,但本地文件还在。队友 git pull 后 Git 会删除 他们工作区里的该文件(因跟踪记录已移除),敏感文件误提交场景要先轮换密钥再执行。完整流程:.gitignore → git rm --cached → commit → 通知团队。可用 git ls-files 查看当前仍被跟踪的文件列表。
实际应用场景:
bash
# 场景1:敏感配置已进仓库 ------ 停止跟踪 + 忽略 + 若已 push 须轮换密钥
git rm --cached config/database.yml # 本地 yml 仍在,远程下次 push 后移除
echo "config/database.yml" >> .gitignore
git add .gitignore
git commit -m "security: remove database config from version control"
# 已泄露的密码在 Git 历史中仍存在,需 filter-repo/BFG + 强制改密
# 场景2:误提交 node_modules(体积大、跨平台路径易冲突)
git rm -r --cached node_modules/ # 不删磁盘,只移出索引
echo "node_modules/" >> .gitignore
git add .gitignore
git commit -m "chore: ignore node_modules directory"
【代码注释】 :误提交 node_modules 或 .env 时,除 rm --cached 外,若已 push 且含密钥须轮换密钥 并考虑 git filter-repo 清历史。队友 pull 后本地该路径会被删除(因不再跟踪),应提前通知「请备份本地配置后 pull」。
实战 .gitignore 规则示例(前端项目常见写法,可直接放入项目根目录试用):
gitignore
# 锚定根目录:只忽略仓库根下的 config.json,不忽略 src/config.json
/config.json
# 先广后窄:先忽略全部 .js,再用 ! 白名单拉回入口(顺序不可颠倒)
*.js
!01.js
# 目录规则:dist/ 只匹配目录名,避免误伤名为 dist 的单文件
dist/
# 构建链生成物:若 .css 由 Sass/Less 编译,源文件在 src/ 可单独跟踪
*.css
【代码注释】 :规则自上而下 匹配,后写的可覆盖先写的;!01.js 必须写在 *.js 之后才能生效。/config.json 只忽略仓库根目录文件;dist/ 尾斜杠只匹配目录。提交前对可疑路径执行 git check-ignore -v src/index.js 确认命中行。
业界实践:
- GitHub :新建仓库可勾选 Node/Python 等模板自动生成
.gitignore - create-react-app / Vite :脚手架自带忽略
node_modules、dist - npm :
npx gitignore node按技术栈生成规则
可运行示例 · 冲突标记识别页(帮助理解合并冲突 UI):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Git 合并冲突标记说明</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 2rem auto; }
.ours { background: #dbeafe; padding: 1rem; margin: 0.5rem 0; }
.theirs { background: #fce7f3; padding: 1rem; margin: 0.5rem 0; }
mark { background: #fde047; }
</style>
</head>
<body>
<h1>冲突文件中的标记</h1>
<p>合并后编辑器中会出现如下结构,删除标记行并保留需要的内容,再 <code>git add</code> + <code>git commit</code>。</p>
<pre class="ours"><<<<<<< HEAD
当前分支的代码
=======</pre>
<pre class="theirs">要合并进来的代码
>>>>>>> feature/branch</pre>
<p>VS Code、WebStorm、Cursor 均提供「接受当前 / 传入 / 两者」的可视化按钮。</p>
</body>
</html>
【代码注释】 :冲突标记 <<<<<<< / ======= / >>>>>>> 由 Git 插入,合并后必须全部删除。仅当两分支修改了同一文件相邻或重叠行才冲突;不同文件或相距较远的同行 Git 常自动合并。IDE 三向对比可减轻手工负担。
3.5 忽略机制模块归纳
| 场景 | 命令 / 操作 |
|---|---|
| 新文件应忽略 | 写入 .gitignore 后勿 add |
| 已跟踪应忽略 | git rm --cached + 提交 |
| 规则是否生效 | git check-ignore -v <path> |
4. Git分支管理策略
分支是Git最强大的功能之一,它允许开发者在独立的代码线上工作,避免相互干扰。
4.1 分支的核心概念
【名词解释】
- 分支 (Branch):指向某个提交对象的可变指针
- HEAD:指向当前分支的指针
- 合并 (Merge):将分支历史整合到一起
- 快进合并 (Fast-forward):当分支可以直接向前移动时的合并方式
- 三方合并 (3-way Merge):当两个分支有分歧时的合并方式
分支与主线的关系(等价于课堂示意图中的「从主线分支出功能线再合并」):
#mermaid-svg-7wwKs2zhAcK9wvQA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7wwKs2zhAcK9wvQA .error-icon{fill:#552222;}#mermaid-svg-7wwKs2zhAcK9wvQA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7wwKs2zhAcK9wvQA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7wwKs2zhAcK9wvQA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7wwKs2zhAcK9wvQA .marker.cross{stroke:#333333;}#mermaid-svg-7wwKs2zhAcK9wvQA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7wwKs2zhAcK9wvQA p{margin:0;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-id,#mermaid-svg-7wwKs2zhAcK9wvQA .commit-msg,#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms',verdana,arial,sans-serif;font-family:var(--mermaid-font-family);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label0{fill:#ffffff;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit0{stroke:hsl(240, 100%, 46.2745098039%);fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight0{stroke:hsl(60, 100%, 3.7254901961%);fill:hsl(60, 100%, 3.7254901961%);}#mermaid-svg-7wwKs2zhAcK9wvQA .label0{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow0{stroke:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label1{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit1{stroke:hsl(60, 100%, 43.5294117647%);fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight1{stroke:rgb(0, 0, 160.5);fill:rgb(0, 0, 160.5);}#mermaid-svg-7wwKs2zhAcK9wvQA .label1{fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow1{stroke:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label2{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit2{stroke:hsl(80, 100%, 46.2745098039%);fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight2{stroke:rgb(48.8333333334, 0, 146.5000000001);fill:rgb(48.8333333334, 0, 146.5000000001);}#mermaid-svg-7wwKs2zhAcK9wvQA .label2{fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow2{stroke:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label3{fill:#ffffff;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit3{stroke:hsl(210, 100%, 46.2745098039%);fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight3{stroke:rgb(146.5000000001, 73.2500000001, 0);fill:rgb(146.5000000001, 73.2500000001, 0);}#mermaid-svg-7wwKs2zhAcK9wvQA .label3{fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow3{stroke:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label4{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit4{stroke:hsl(180, 100%, 46.2745098039%);fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight4{stroke:rgb(146.5000000001, 0, 0);fill:rgb(146.5000000001, 0, 0);}#mermaid-svg-7wwKs2zhAcK9wvQA .label4{fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow4{stroke:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label5{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit5{stroke:hsl(150, 100%, 46.2745098039%);fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight5{stroke:rgb(146.5000000001, 0, 73.2500000001);fill:rgb(146.5000000001, 0, 73.2500000001);}#mermaid-svg-7wwKs2zhAcK9wvQA .label5{fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow5{stroke:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label6{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit6{stroke:hsl(300, 100%, 46.2745098039%);fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight6{stroke:rgb(0, 146.5000000001, 0);fill:rgb(0, 146.5000000001, 0);}#mermaid-svg-7wwKs2zhAcK9wvQA .label6{fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow6{stroke:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch-label7{fill:black;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit7{stroke:hsl(0, 100%, 46.2745098039%);fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight7{stroke:rgb(0, 146.5000000001, 146.5000000001);fill:rgb(0, 146.5000000001, 146.5000000001);}#mermaid-svg-7wwKs2zhAcK9wvQA .label7{fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow7{stroke:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .branch{stroke-width:1;stroke:#333333;stroke-dasharray:2;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-label{font-size:10px;fill:#000021;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-label-bkg{font-size:10px;fill:#ffffde;opacity:0.5;}#mermaid-svg-7wwKs2zhAcK9wvQA .tag-label{font-size:10px;fill:#131300;}#mermaid-svg-7wwKs2zhAcK9wvQA .tag-label-bkg{fill:#ECECFF;stroke:hsl(240, 60%, 86.2745098039%);}#mermaid-svg-7wwKs2zhAcK9wvQA .tag-hole{fill:#333;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-merge{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-reverse{stroke:#ECECFF;fill:#ECECFF;stroke-width:3;}#mermaid-svg-7wwKs2zhAcK9wvQA .commit-highlight-inner{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-7wwKs2zhAcK9wvQA .arrow{stroke-width:8;stroke-linecap:round;fill:none;}#mermaid-svg-7wwKs2zhAcK9wvQA .gitTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7wwKs2zhAcK9wvQA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main develop feature/shopcart feature/order m1 d1 s1 o1 release
若本地有
images/分支.jpg示意图,可与上图对照理解;无图时以上 mermaid 即可表达分支拓扑。
4.2 分支操作完整指南
bash
# ── 查看分支(* 号为当前检出分支)──
git branch # 仅本地分支列表
git branch -r # 远程跟踪分支,形如 origin/main
git branch -a # 本地 + 远程跟踪分支一并列出
git branch -vv # 附带上游跟踪关系及领先/落后提交数
# ── 创建与切换(新分支从当前 HEAD 分出)──
git branch <branch-name> # 只创建指针,不切换;基于当前 commit
git switch <branch-name> # 切换分支;工作区有冲突改动时可能拒绝
git switch -c <new-branch> # 创建并切换,等价于 checkout -b
# ── 删除与重命名 ──
git branch -d <branch-name> # 安全删除:仅当该分支已合并到当前分支
git branch -D <branch-name> # 强制删除:丢弃未合并提交(慎用)
git branch -m <old> <new> # 重命名当前或指定本地分支
【代码注释】 :git switch(2.23+)专用于切换分支;git restore 专用于恢复文件;旧版 git checkout 兼管二者易混淆。切换前 git status 应干净,否则未提交改动会随分支带走 或阻塞切换。git branch -d 删除的是指针 ,commit 仍在 reflog 中短期可查。远程分支删除用 git push origin --delete <branch>,与本地 -d 无关。
4.3 分支操作工作流
#mermaid-svg-FjhQTagyz8IOdjbj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FjhQTagyz8IOdjbj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FjhQTagyz8IOdjbj .error-icon{fill:#552222;}#mermaid-svg-FjhQTagyz8IOdjbj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FjhQTagyz8IOdjbj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FjhQTagyz8IOdjbj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FjhQTagyz8IOdjbj .marker.cross{stroke:#333333;}#mermaid-svg-FjhQTagyz8IOdjbj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FjhQTagyz8IOdjbj p{margin:0;}#mermaid-svg-FjhQTagyz8IOdjbj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FjhQTagyz8IOdjbj .cluster-label text{fill:#333;}#mermaid-svg-FjhQTagyz8IOdjbj .cluster-label span{color:#333;}#mermaid-svg-FjhQTagyz8IOdjbj .cluster-label span p{background-color:transparent;}#mermaid-svg-FjhQTagyz8IOdjbj .label text,#mermaid-svg-FjhQTagyz8IOdjbj span{fill:#333;color:#333;}#mermaid-svg-FjhQTagyz8IOdjbj .node rect,#mermaid-svg-FjhQTagyz8IOdjbj .node circle,#mermaid-svg-FjhQTagyz8IOdjbj .node ellipse,#mermaid-svg-FjhQTagyz8IOdjbj .node polygon,#mermaid-svg-FjhQTagyz8IOdjbj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FjhQTagyz8IOdjbj .rough-node .label text,#mermaid-svg-FjhQTagyz8IOdjbj .node .label text,#mermaid-svg-FjhQTagyz8IOdjbj .image-shape .label,#mermaid-svg-FjhQTagyz8IOdjbj .icon-shape .label{text-anchor:middle;}#mermaid-svg-FjhQTagyz8IOdjbj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FjhQTagyz8IOdjbj .rough-node .label,#mermaid-svg-FjhQTagyz8IOdjbj .node .label,#mermaid-svg-FjhQTagyz8IOdjbj .image-shape .label,#mermaid-svg-FjhQTagyz8IOdjbj .icon-shape .label{text-align:center;}#mermaid-svg-FjhQTagyz8IOdjbj .node.clickable{cursor:pointer;}#mermaid-svg-FjhQTagyz8IOdjbj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FjhQTagyz8IOdjbj .arrowheadPath{fill:#333333;}#mermaid-svg-FjhQTagyz8IOdjbj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FjhQTagyz8IOdjbj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FjhQTagyz8IOdjbj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FjhQTagyz8IOdjbj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FjhQTagyz8IOdjbj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FjhQTagyz8IOdjbj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FjhQTagyz8IOdjbj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FjhQTagyz8IOdjbj .cluster text{fill:#333;}#mermaid-svg-FjhQTagyz8IOdjbj .cluster span{color:#333;}#mermaid-svg-FjhQTagyz8IOdjbj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FjhQTagyz8IOdjbj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FjhQTagyz8IOdjbj rect.text{fill:none;stroke-width:0;}#mermaid-svg-FjhQTagyz8IOdjbj .icon-shape,#mermaid-svg-FjhQTagyz8IOdjbj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FjhQTagyz8IOdjbj .icon-shape p,#mermaid-svg-FjhQTagyz8IOdjbj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FjhQTagyz8IOdjbj .icon-shape .label rect,#mermaid-svg-FjhQTagyz8IOdjbj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FjhQTagyz8IOdjbj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FjhQTagyz8IOdjbj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FjhQTagyz8IOdjbj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建功能分支
开发登录功能
功能完成
合并功能分支
删除功能分支
主分支 master
feature/login
提交变更
切换回主分支
分支被删除
4.4 分支合并与冲突解决
4.4.1 合并分支基础
bash
# 将 <branch-name> 的历史并入「当前所在分支」(先 switch 到接收方,再 merge 来源方)
git merge <branch-name>
# 示例:把 feature/login 合进 master(须在 master 上执行 merge)
git switch master # 接收合并结果的目标分支
git merge feature/login # 无分叉时可能 fast-forward,有分叉则三方合并
# --no-ff:即使可快进也强制生成 merge commit,保留「曾有过 feature 分支」的拓扑
git merge --no-ff feature/login # 团队规范常用,便于 revert 整次功能
# --squash:把 feature 上所有改动压成一次 staged 变更,需再手动 commit
git merge --squash feature/login
git commit -m "feat: login module" # squash 不会自动提交
【代码注释】 :合并方向口诀:站在要并入的分支上,merge 来源分支 。Fast-forward 时无 merge commit,历史呈直线;--no-ff 多一个合并节点,方便按功能整体回滚。--squash 适合 PR 前整理提交,但会丢失 feature 上的逐条 commit 作者与时间。冲突时 Git 暂停合并,解决后 git add + git commit(或 git merge --abort 放弃)。
4.4.2 合并冲突解决
当Git无法自动合并时,需要手动解决冲突。
bash
# 1. merge/rebase 中断后,冲突文件会标为 both modified
git status # Unmerged paths 下列出待解决文件
# 2. 在编辑器中处理冲突标记(三者必须全部删除,不能只留标记):
# <<<<<<< HEAD ← 当前检出分支(合并接收方)的内容
# 当前分支的代码
# ======= ← 分隔线
# 要合并进来的分支的代码
# >>>>>>> feature/login ← 被 merge 进来的分支名
# 3. 保存人工合并结果后,加入暂存区表示「此文件冲突已解决」
git add <conflicted-file> # 每个冲突文件都要 add;可用 git add .
# 4. 完成合并(若 merge 已启动,一般无需 -m,Git 自带默认合并说明)
git commit # 或 merge 过程中已自动进入编辑器
# 放弃本次合并:git merge --abort
【代码注释】 :冲突只发生在同一文件相邻或重叠行 ;不同文件 Git 常自动合并。HEAD 在 merge 时指当前分支 (你 switch 过去的那条)。可用 git checkout --ours/--theirs <file> 快速选一侧,但复杂逻辑仍建议手改。VS Code / Cursor 的「Accept Current / Incoming / Both」对应上述三区。解决后务必跑测试再 commit;git merge --abort 可回到合并前状态。
实际冲突解决示例:
javascript
// 冲突前:两分支从同一祖先各自演进,Git 无法自动决定保留哪种业务逻辑
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// <<<<<<< HEAD ------ 当前分支(master)在合并时引入的「折扣」实现
function calculateTotal(items, discount = 0) {
const total = items.reduce((sum, item) => sum + item.price, 0);
return total * (1 - discount);
}
// ======= ------ 分隔符,不是注释,合并时必须整段删除
// >>>>>>> feature/tax-calculation 的另一侧:「税费」实现
function calculateTotal(items, tax = 0.1) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 + tax);
}
// >>>>>>> feature/tax-calculation
// 解决后:手工合并两特性------先折扣后税费(顺序需与产品规则一致)
function calculateTotal(items, discount = 0, tax = 0.1) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 - discount) * (1 + tax);
}
【代码注释】 :冲突标记是 Git 插入的文本 ,留在源码中会导致语法错误与安全风险。解决原则:理解两侧业务意图后合成 合理实现,而非简单二选一。合并后执行 git diff --check 可扫描是否残留 <<<<<<<;跑单元测试确认折扣与税费计算顺序符合需求,再 git add + git commit。
4.5 实际项目分支策略
bash
# 【电商项目分支管理示例】master 稳定 + develop 集成 + feature 并行
# 1. 同步主线,确保 feature 从最新稳定点分出
git switch master
git pull origin master # 拉远程 master,减少合并时大范围冲突
# 2. 购物车功能:从 master 或 develop 创建独立分支(课堂常从 master 分)
git switch -c feature/shopcart # -c:创建并切换,分支 tip = 当前 HEAD
# 3. 在 feature 上小步提交,便于 Code Review 与回滚
git add .
git commit -m "feat: add shopping cart functionality"
# 4. 模拟另一开发者:回到 master 再开订单分支(与 shopcart 并行)
git switch master
git switch -c feature/order
# 5. 订单模块独立提交,不与 shopcart 混在同一分支
git add .
git commit -m "feat: add order management"
# 6. 集成分支:先切到 develop,再 merge 各 feature(接收方是 develop)
git switch develop
git merge feature/shopcart # 若有冲突在此解决
git merge feature/order
# 7. 已合并的 feature 可删本地指针,远程可选 push --delete
git branch -d feature/shopcart
git branch -d feature/order
【代码注释】 :git switch 时未提交改动会随你带到新分支 (若文件在两分支都存在),易造成「在 feature 上改却在 main 上提交」的混乱。切换前 git status 应干净,或 git stash push -m "..."。删除分支前确认已 merge 或已 push 备份。
4.6 git rebase:变基操作详解
rebase 将当前分支的提交"重新播放"到目标分支顶端,产生线性历史,视觉上更整洁。
rebase vs merge 对比
#mermaid-svg-oniCYZOqmievzZqs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oniCYZOqmievzZqs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oniCYZOqmievzZqs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oniCYZOqmievzZqs .error-icon{fill:#552222;}#mermaid-svg-oniCYZOqmievzZqs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oniCYZOqmievzZqs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oniCYZOqmievzZqs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oniCYZOqmievzZqs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oniCYZOqmievzZqs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oniCYZOqmievzZqs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oniCYZOqmievzZqs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oniCYZOqmievzZqs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oniCYZOqmievzZqs .marker.cross{stroke:#333333;}#mermaid-svg-oniCYZOqmievzZqs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oniCYZOqmievzZqs p{margin:0;}#mermaid-svg-oniCYZOqmievzZqs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oniCYZOqmievzZqs .cluster-label text{fill:#333;}#mermaid-svg-oniCYZOqmievzZqs .cluster-label span{color:#333;}#mermaid-svg-oniCYZOqmievzZqs .cluster-label span p{background-color:transparent;}#mermaid-svg-oniCYZOqmievzZqs .label text,#mermaid-svg-oniCYZOqmievzZqs span{fill:#333;color:#333;}#mermaid-svg-oniCYZOqmievzZqs .node rect,#mermaid-svg-oniCYZOqmievzZqs .node circle,#mermaid-svg-oniCYZOqmievzZqs .node ellipse,#mermaid-svg-oniCYZOqmievzZqs .node polygon,#mermaid-svg-oniCYZOqmievzZqs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oniCYZOqmievzZqs .rough-node .label text,#mermaid-svg-oniCYZOqmievzZqs .node .label text,#mermaid-svg-oniCYZOqmievzZqs .image-shape .label,#mermaid-svg-oniCYZOqmievzZqs .icon-shape .label{text-anchor:middle;}#mermaid-svg-oniCYZOqmievzZqs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oniCYZOqmievzZqs .rough-node .label,#mermaid-svg-oniCYZOqmievzZqs .node .label,#mermaid-svg-oniCYZOqmievzZqs .image-shape .label,#mermaid-svg-oniCYZOqmievzZqs .icon-shape .label{text-align:center;}#mermaid-svg-oniCYZOqmievzZqs .node.clickable{cursor:pointer;}#mermaid-svg-oniCYZOqmievzZqs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oniCYZOqmievzZqs .arrowheadPath{fill:#333333;}#mermaid-svg-oniCYZOqmievzZqs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oniCYZOqmievzZqs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oniCYZOqmievzZqs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oniCYZOqmievzZqs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oniCYZOqmievzZqs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oniCYZOqmievzZqs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oniCYZOqmievzZqs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oniCYZOqmievzZqs .cluster text{fill:#333;}#mermaid-svg-oniCYZOqmievzZqs .cluster span{color:#333;}#mermaid-svg-oniCYZOqmievzZqs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oniCYZOqmievzZqs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oniCYZOqmievzZqs rect.text{fill:none;stroke-width:0;}#mermaid-svg-oniCYZOqmievzZqs .icon-shape,#mermaid-svg-oniCYZOqmievzZqs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oniCYZOqmievzZqs .icon-shape p,#mermaid-svg-oniCYZOqmievzZqs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oniCYZOqmievzZqs .icon-shape .label rect,#mermaid-svg-oniCYZOqmievzZqs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oniCYZOqmievzZqs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oniCYZOqmievzZqs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oniCYZOqmievzZqs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} rebase(线性历史)
main: A→B→C
feat rebase 后: A→B→C→D'→E'
merge(保留分叉历史)
merge
main: A→B→C
main: A→B→C→M(合并提交)
feat: A→D→E
bash
# 【基础变基】把 feature 上的提交「挪到」main 最新 tip 之后,形成线性历史
git switch feature/login # 在要被变基的分支上操作
git fetch origin main # 先更新参照分支(若 main 在远程)
git rebase main # 等价于:摘下 feature 的 commit,接到 main 顶端重放
# Git 按提交顺序逐个应用;某步冲突 → 改文件 → git add → git rebase --continue
# 变基冲突处理循环
git add <resolved-file>
git rebase --continue # 当前提交解决后,继续下一个
git rebase --skip # 跳过当前提交(慎用,会丢该 commit 的改动)
git rebase --abort # 放弃整个 rebase,回到 rebase 开始前状态
# 【交互式变基 -i】重写最近 N 条提交:压缩、改 message、调序、删除
git rebase -i HEAD~4 # 打开编辑器,对 4 条提交使用 pick/squash/reword 等
交互式变基操作指令:
| 指令 | 作用 |
|---|---|
pick |
保留该提交(默认) |
reword |
保留提交但修改 message |
squash |
合并到上一个提交(合并 message) |
fixup |
合并到上一个提交(丢弃本 message) |
drop |
删除该提交 |
edit |
暂停以修改提交内容 |
经典场景:PR 前清理本地提交历史
bash
# 开发过程中产生了很多琐碎提交
git log --oneline feature/payment
# abc1234 fix typo
# def5678 fix lint error
# ghi9012 wip: payment api
# jkl3456 feat: payment module init
# 合并到 PR 前,将 4 个提交压缩为 1 个
git rebase -i HEAD~4
# 将前 3 个改为 fixup,只保留最后一个 pick
# 结果:一个干净的 "feat: payment module" 提交
⚠️ rebase 黄金法则 :绝不对已推送到共享分支的提交做 rebase。Rebase 会改写提交哈希,强推后会让团队其他人的本地仓库历史与远程产生分歧,造成严重混乱。
bash
# ✅ 个人 feature:变基到最新 main,再安全强推(改写的是自己的提交哈希)
git switch feature/my-feature
git fetch origin
git rebase origin/main # 将 feature 上的 commit 接到 main 最新 tip 之后
git push --force-with-lease origin feature/my-feature
# --force-with-lease:若远程已有他人新提交则拒绝强推,比 --force 更安全
# ❌ 禁止:在共享分支 main 上 rebase 功能分支 → 改写全队历史
git switch main
git rebase feature # 会导致所有人 pull 困难,甚至丢提交
4.7 git cherry-pick:精准摘取提交
cherry-pick 将指定提交的改动应用到当前分支,适合跨分支"搬运"特定修复。
bash
# 将指定提交的 patch 应用到当前分支顶端(生成新 commit,哈希与源提交不同)
git cherry-pick abc1234
# 按顺序摘取多个离散提交
git cherry-pick abc1234 def5678
# 范围:左开右闭 (abc1234, def5678],不含 abc1234 本身
git cherry-pick abc1234..def5678
# 只应用改动到工作区/暂存区,不自动 commit(便于批量调整后一次提交)
git cherry-pick --no-commit abc1234
git status # 确认 diff 无误后再 git commit
# 冲突后
git cherry-pick --continue # 同 merge:解决 → add → continue
git cherry-pick --abort # 放弃本次 cherry-pick,回到操作前
经典场景一:将 bug 修复同步到多个维护版本
bash
# 多版本线维护:同一 bugfix 需落到 v2、v3 两条 release 分支
git switch release/v3
git log --oneline -3
# abc1234 fix: null pointer in checkout flow ← 在 v3 上已验证的修复
git switch release/v2 # 切到旧版本维护分支
git cherry-pick abc1234 # 应用相同 diff;冲突则说明 v2 代码结构不同,需手改
git push origin release/v2 # 新 commit 哈希 ≠ abc1234,但 patch 内容一致
经典场景二:从废弃分支中救回有用的提交
bash
# 废弃分支中「单 commit」有价值,不必整分支 merge 进来
git log --oneline feature/experiment
# xyz9876 refactor: extract date formatter utility ← 仅摘这一条
# abc1111 feat: experimental feature (整段实验不要)
git switch main
git cherry-pick xyz9876 # 只引入该提交的改动;与 merge 整分支相比历史更干净
# 若 xyz9876 依赖同分支其它提交,cherry-pick 可能冲突,需补依赖或手改
4.8 Detached HEAD 状态解析
切换到标签或直接检出某个 commit 哈希时,HEAD 不再指向任何分支,而是直接指向一个提交------这就是 **Detached HEAD(游离 HEAD)**状态。
bash
# 以下操作使 HEAD 直接指向 commit/tag,而非分支名 → Detached HEAD
git switch --detach v1.0.0 # 检出标签快照(Git 2.23+ 推荐写法)
git switch --detach abc1234 # 检出指定哈希,用于审查历史
git switch --detach HEAD~3 # 检出祖先,临时对比旧版代码
# 终端警告含义:当前不在任何分支上,在此状态下的新提交无分支引用保护
# HEAD is now at abc1234...
# You are in 'detached HEAD' state.
Detached HEAD 下的操作规则:
bash
# ✅ 只读:查看历史文件、编译旧版、跑对比测试,不产生新 commit 则无风险
git switch --detach v1.2.0
cat src/utils.js # 读的是 v1.2.0 树中的内容,非工作区最新编辑
# ⚠️ 危险:在游离态 git commit → 新提交只被 HEAD 引用,一切走分支即「悬空」
# git gc 后可能被回收,相当于丢提交
# ✅ 若必须在旧版本上改代码:第一时间建分支,把 HEAD 钉在分支上
git switch -c hotfix/from-v1.2.0 # 从当前游离 commit 拉出分支
git add .
git commit -m "fix: patch for v1.2.0"
从 Detached HEAD 恢复:
bash
# 方法1:丢弃游离态,回到已有分支(未在游离态提交时)
git switch main
# 方法2:游离态已有有用提交,用当前 HEAD 创建分支保存
git switch -c temp-branch # 当前 commit 成为 temp-branch 的 tip
# 方法3:已切走导致「丢失」的游离提交,用 reflog 按哈希恢复
git reflog
# abc1234 HEAD@{1}: commit: my work in detached state
git switch -c rescued-branch abc1234
4.9 电商项目多分支协作流程
典型前端电商演练(与课堂 shopcart / order 场景一致):
#mermaid-svg-6KQwSsw2aRGLihHS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6KQwSsw2aRGLihHS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6KQwSsw2aRGLihHS .error-icon{fill:#552222;}#mermaid-svg-6KQwSsw2aRGLihHS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6KQwSsw2aRGLihHS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6KQwSsw2aRGLihHS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6KQwSsw2aRGLihHS .marker.cross{stroke:#333333;}#mermaid-svg-6KQwSsw2aRGLihHS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6KQwSsw2aRGLihHS p{margin:0;}#mermaid-svg-6KQwSsw2aRGLihHS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6KQwSsw2aRGLihHS .cluster-label text{fill:#333;}#mermaid-svg-6KQwSsw2aRGLihHS .cluster-label span{color:#333;}#mermaid-svg-6KQwSsw2aRGLihHS .cluster-label span p{background-color:transparent;}#mermaid-svg-6KQwSsw2aRGLihHS .label text,#mermaid-svg-6KQwSsw2aRGLihHS span{fill:#333;color:#333;}#mermaid-svg-6KQwSsw2aRGLihHS .node rect,#mermaid-svg-6KQwSsw2aRGLihHS .node circle,#mermaid-svg-6KQwSsw2aRGLihHS .node ellipse,#mermaid-svg-6KQwSsw2aRGLihHS .node polygon,#mermaid-svg-6KQwSsw2aRGLihHS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6KQwSsw2aRGLihHS .rough-node .label text,#mermaid-svg-6KQwSsw2aRGLihHS .node .label text,#mermaid-svg-6KQwSsw2aRGLihHS .image-shape .label,#mermaid-svg-6KQwSsw2aRGLihHS .icon-shape .label{text-anchor:middle;}#mermaid-svg-6KQwSsw2aRGLihHS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6KQwSsw2aRGLihHS .rough-node .label,#mermaid-svg-6KQwSsw2aRGLihHS .node .label,#mermaid-svg-6KQwSsw2aRGLihHS .image-shape .label,#mermaid-svg-6KQwSsw2aRGLihHS .icon-shape .label{text-align:center;}#mermaid-svg-6KQwSsw2aRGLihHS .node.clickable{cursor:pointer;}#mermaid-svg-6KQwSsw2aRGLihHS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6KQwSsw2aRGLihHS .arrowheadPath{fill:#333333;}#mermaid-svg-6KQwSsw2aRGLihHS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6KQwSsw2aRGLihHS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6KQwSsw2aRGLihHS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6KQwSsw2aRGLihHS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6KQwSsw2aRGLihHS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6KQwSsw2aRGLihHS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6KQwSsw2aRGLihHS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6KQwSsw2aRGLihHS .cluster text{fill:#333;}#mermaid-svg-6KQwSsw2aRGLihHS .cluster span{color:#333;}#mermaid-svg-6KQwSsw2aRGLihHS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6KQwSsw2aRGLihHS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6KQwSsw2aRGLihHS rect.text{fill:none;stroke-width:0;}#mermaid-svg-6KQwSsw2aRGLihHS .icon-shape,#mermaid-svg-6KQwSsw2aRGLihHS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6KQwSsw2aRGLihHS .icon-shape p,#mermaid-svg-6KQwSsw2aRGLihHS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6KQwSsw2aRGLihHS .icon-shape .label rect,#mermaid-svg-6KQwSsw2aRGLihHS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6KQwSsw2aRGLihHS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6KQwSsw2aRGLihHS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6KQwSsw2aRGLihHS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} merge
merge
测试通过 merge
master 稳定
develop 集成分支
feature/shopcart 购物车
feature/order 订单
bash
# 1. 集成分支先与远程同步,再基于最新 develop 开 feature
git switch develop
git pull origin develop
# 2. 购物车:从 develop 分出 → 开发 → 合回 develop → 删本地 feature
git switch -c feature/shopcart # 基于当前 develop tip
git add .
git commit -m "feat: shopping cart module"
git switch develop # 合并接收方必须是 develop
git merge feature/shopcart
git branch -d feature/shopcart
# 3. 订单:同上,与 shopcart 并行、互不影响直到 merge 进 develop
git switch -c feature/order
git add .
git commit -m "feat: order module"
git switch develop
git merge feature/order
git branch -d feature/order
# 4. 测试通过后,develop → master 发布;push 触发部署
git switch master
git merge develop # 生产线只收已测过的集成结果
git push origin master
【代码注释】 :课堂 02-project 等多含 master/develop 与 shopcart、order 等 feature 历史。用 git log --graph --oneline --all --decorate 对照本节流程,可直观看到 merge 节点与分支指针移动;配合 git branch -a 查看本地与 origin/* 跟踪分支。
4.10 分支模块归纳
| 操作 | 命令 | 注意 |
|---|---|---|
| 创建 | git branch name / git switch -c name |
基于当前 HEAD |
| 切换 | git switch name |
工作区须干净 |
| 合并 | git merge name |
先切到被并入分支 |
| 删除 | git branch -d name |
未合并用 -D |
| 变基 | git rebase main |
仅限个人分支,勿对共享分支使用 |
| 摘取 | git cherry-pick <hash> |
跨分支搬运特定提交 |
| 游离恢复 | git switch -c new-branch |
Detached HEAD 下立刻创建分支保存工作 |
5. 远程仓库与协作开发
远程仓库是团队协作的基础,GitHub作为全球最大的代码托管平台,为开发者提供了强大的协作工具。
5.1 GitHub平台详解
**【专业背景】**GitHub于2008年上线,2018年被微软以75亿美元收购。截至2024年,GitHub拥有超过1亿注册用户,托管着超过3.3亿个代码仓库,是全球最大的开源社区。
核心功能:
- 代码托管:Git 仓库的远程存储
- 协作开发:Pull Request、Code Review
- 项目管理:Issues、Projects、Actions
- 社区建设:Stars、Forks、Watchers
业界平台对照:
| 平台 | 特点 | 典型场景 |
|---|---|---|
| GitHub | 开源生态最大、Actions CI | 开源库、国际化协作 |
| GitLab | 内置 CI/CD、私有化部署 | 企业内网、DevOps 一体 |
| Gitee | 国内访问快 | 国内团队、教学演示 |
| Bitbucket | 与 Jira 集成 | Atlassian 技术栈团队 |
探索高星仓库 :在 GitHub 搜索框输入 stars:>100000 可按 Star 数筛选热门项目,学习 .gitignore、CI 与分支策略。
5.2 远程仓库配置操作
bash
# ── 远程仓库(remote)配置:本地分支与托管平台之间的命名与 URL 映射 ──
git remote # 列出远程短名,clone 后默认有 origin
git remote -v # -v:同时显示 fetch/push 两套 URL(HTTPS 或 SSH)
git remote add origin https://github.com/username/project.git
# 首次关联;origin 为约定俗成默认名,也可 add upstream 指向 fork 源仓库
git remote remove origin # 仅删除本地 remote 配置,不删除 GitHub 上的仓库
git remote rename origin upstream # 例如 fork 后把原仓库改为 upstream
git remote set-url origin git@github.com:username/new-project.git
# 在 HTTPS ↔ SSH 之间切换,或更换仓库地址
git remote show origin # 显示默认分支、跟踪分支、push/pull 行为摘要
【代码注释】 :remote 是本地 .git/config 里的别名 + URL ,不是远程仓库本身。Fork 工作流常见:origin 指向自己的 fork,upstream 指向官方原仓,git fetch upstream 同步主线。git remote show 可查看 origin/main 等跟踪分支的 ahead/behind。团队应统一 HTTPS 或 SSH,避免有人用 token、有人用密钥导致权限混乱。
5.3 git fetch vs git pull:核心区别详解
这是团队协作中最容易混淆的概念之一,理解二者差异是写出安全协作流程的前提。
5.3.0 原理对比
git fetch origin
↓
只下载远程数据到本地「远程跟踪分支」(origin/main 等)
不修改任何本地工作分支
工作区/暂存区完全不变
git pull origin main
↓ 等价于
git fetch origin # 先下载
git merge origin/main # 再自动合并到当前分支(或 rebase,取决于配置)
bash
# ── git fetch:只同步数据,不修改当前分支(最安全的同步方式)──
git fetch origin # 下载所有远程分支的最新提交
git fetch origin main # 只下载 main 分支(减少带宽)
# 查看「别人做了什么」而不影响自己的工作
git log HEAD..origin/main --oneline # 列出远程比本地多的提交(即将被 merge 的内容)
git diff HEAD origin/main # 对比本地与远程的代码差异
# 决定好后,再手动整合(merge 或 rebase)
git merge origin/main # 把远程更新合并进当前本地分支
# 或:git rebase origin/main # 以变基方式整合,线性历史
# ── git pull:fetch + 自动整合(默认 merge,推荐改为 rebase)──
git pull origin main # 默认:fetch + merge,可能产生 merge bubble
git pull --rebase origin main # fetch + rebase,保持线性历史(推荐)
git pull # 已设置 upstream 时,省略 remote 与分支名
# 全局设为 rebase 模式(推荐)
git config --global pull.rebase true
# 之后 git pull 默认行为等同于 git pull --rebase
何时用 fetch,何时用 pull?
#mermaid-svg-s4W8otSmjjar7cXR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s4W8otSmjjar7cXR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s4W8otSmjjar7cXR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s4W8otSmjjar7cXR .error-icon{fill:#552222;}#mermaid-svg-s4W8otSmjjar7cXR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s4W8otSmjjar7cXR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s4W8otSmjjar7cXR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s4W8otSmjjar7cXR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s4W8otSmjjar7cXR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s4W8otSmjjar7cXR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s4W8otSmjjar7cXR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s4W8otSmjjar7cXR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s4W8otSmjjar7cXR .marker.cross{stroke:#333333;}#mermaid-svg-s4W8otSmjjar7cXR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s4W8otSmjjar7cXR p{margin:0;}#mermaid-svg-s4W8otSmjjar7cXR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s4W8otSmjjar7cXR .cluster-label text{fill:#333;}#mermaid-svg-s4W8otSmjjar7cXR .cluster-label span{color:#333;}#mermaid-svg-s4W8otSmjjar7cXR .cluster-label span p{background-color:transparent;}#mermaid-svg-s4W8otSmjjar7cXR .label text,#mermaid-svg-s4W8otSmjjar7cXR span{fill:#333;color:#333;}#mermaid-svg-s4W8otSmjjar7cXR .node rect,#mermaid-svg-s4W8otSmjjar7cXR .node circle,#mermaid-svg-s4W8otSmjjar7cXR .node ellipse,#mermaid-svg-s4W8otSmjjar7cXR .node polygon,#mermaid-svg-s4W8otSmjjar7cXR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s4W8otSmjjar7cXR .rough-node .label text,#mermaid-svg-s4W8otSmjjar7cXR .node .label text,#mermaid-svg-s4W8otSmjjar7cXR .image-shape .label,#mermaid-svg-s4W8otSmjjar7cXR .icon-shape .label{text-anchor:middle;}#mermaid-svg-s4W8otSmjjar7cXR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s4W8otSmjjar7cXR .rough-node .label,#mermaid-svg-s4W8otSmjjar7cXR .node .label,#mermaid-svg-s4W8otSmjjar7cXR .image-shape .label,#mermaid-svg-s4W8otSmjjar7cXR .icon-shape .label{text-align:center;}#mermaid-svg-s4W8otSmjjar7cXR .node.clickable{cursor:pointer;}#mermaid-svg-s4W8otSmjjar7cXR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s4W8otSmjjar7cXR .arrowheadPath{fill:#333333;}#mermaid-svg-s4W8otSmjjar7cXR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s4W8otSmjjar7cXR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s4W8otSmjjar7cXR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4W8otSmjjar7cXR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s4W8otSmjjar7cXR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4W8otSmjjar7cXR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s4W8otSmjjar7cXR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s4W8otSmjjar7cXR .cluster text{fill:#333;}#mermaid-svg-s4W8otSmjjar7cXR .cluster span{color:#333;}#mermaid-svg-s4W8otSmjjar7cXR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s4W8otSmjjar7cXR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s4W8otSmjjar7cXR rect.text{fill:none;stroke-width:0;}#mermaid-svg-s4W8otSmjjar7cXR .icon-shape,#mermaid-svg-s4W8otSmjjar7cXR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4W8otSmjjar7cXR .icon-shape p,#mermaid-svg-s4W8otSmjjar7cXR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s4W8otSmjjar7cXR .icon-shape .label rect,#mermaid-svg-s4W8otSmjjar7cXR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4W8otSmjjar7cXR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s4W8otSmjjar7cXR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s4W8otSmjjar7cXR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否,直接同步
有改动
干净
需要同步远程更新
是否想先看看别人做了什么?
git fetch → 查看 diff/log → 再手动 merge/rebase
当前分支是否有未提交改动?
git stash → git pull --rebase → git stash pop
git pull --rebase
更安全,Code Review 友好
快捷,适合个人功能分支日常同步
经典场景一:同步主线前预览变更
bash
# 早上上班,想知道队友昨天合并了什么,再决定是否同步
git fetch origin
# 查看远程 main 比本地多的提交列表
git log main..origin/main --oneline --decorate
# abc1234 feat: add dark mode toggle (张三, 3 hours ago)
# def5678 fix: cart quantity overflow bug (李四, 5 hours ago)
# 查看具体 diff(确认没有与自己工作冲突的改动)
git diff main origin/main -- src/components/
# 确认无冲突后,安全整合
git rebase origin/main
经典场景二:pull 冲突恢复
bash
# git pull 遭遇冲突时(rebase 模式下)
git pull --rebase origin main
# CONFLICT (content): Merge conflict in src/api/user.js
# error: could not apply abc1234... feat: add user profile API
# 解决冲突
# 编辑 src/api/user.js,删除冲突标记,保留正确代码
git add src/api/user.js
git rebase --continue # 继续应用下一个 commit
# 若冲突过多想放弃
git rebase --abort # 回到 pull --rebase 之前的状态
【代码注释】 :git pull 的本质是「fetch + 整合」,整合方式由 pull.rebase 配置决定;git fetch 只是下载,整合权在开发者手中。推荐工作流 :git fetch → git log HEAD..origin/main → git rebase origin/main,比直接 pull 多一步审查,避免意外覆盖本地提交。在 CI/CD 场景中,流水线通常用 git fetch --all + git checkout 而不用 pull,确保精确控制检出版本。
5.4 场景化操作流程
5.4.1 场景一:本地已有项目,推送到GitHub
bash
# 1. 在 GitHub 网页创建空仓库(不要勾选 README,避免与本地首次 push 冲突)
# 2. 进入已有本地仓库目录,关联远程
cd /path/to/your/project
git remote add origin https://github.com/username/project.git
# 若已存在 origin:用 git remote set-url origin <新地址>
# 3. 确保工作区已提交(未提交文件不会出现在远程)
git add .
git commit -m "Initial commit"
# 4. 首次推送并建立上游跟踪(upstream)
git push -u origin main # -u 写入 branch.main.remote / merge,之后可简写 git push
# 旧仓库可能仍用 master:git push -u origin master
# 5. 同一分支后续推送(已设置 upstream 时)
git push # 等价于 git push origin main
【代码注释】 :-u(--set-upstream)在 .git/config 记录 branch.<name>.merge,git status 会显示 ahead/behind。GitHub 2020 起默认主分支名为 main;本地若叫 master 可在推送时用 git push -u origin master:main 对齐。首次 push 若远程已有 README,需先 git pull origin main --rebase 再 push,或强制(仅个人空项目)--force。
可运行示例 · 远程仓库配置清单:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>远程仓库两种场景</title>
<style>body{font-family:system-ui;max-width:700px;margin:2rem auto} pre{background:#1e293b;color:#e2e8f0;padding:1rem;border-radius:8px}</style>
</head>
<body>
<h1>场景 A:本地已有 → 推远程</h1>
<pre>git remote add origin <HTTPS或SSH地址>
git push -u origin main</pre>
<h1>场景 B:远程已有 → 克隆</h1>
<pre>git clone <地址>
cd 项目目录
git switch -c dev origin/dev</pre>
<p>克隆后仅默认检出主分支;其他分支用 <code>git branch -a</code> 查看,<code>git switch -c 本地名 origin/远程名</code> 跟踪。</p>
</body>
</html>
【代码注释】 :git remote rename 只改本地别名,远程仓库不变。set-url 可在 HTTPS(需 PAT/token)与 SSH 间切换;企业代理环境有时仅 HTTPS 可用。克隆后 git branch -a 查看 remotes/origin/*,用 git switch -c 本地名 origin/远程名 建立跟踪分支。
5.4.2 场景二:克隆GitHub项目到本地
bash
# 1. 克隆远程仓库
git clone https://github.com/username/project.git
cd project
# 2. 查看所有分支(包括远程分支)
git branch -a
# 3. 创建并切换到远程开发分支
git switch -c develop origin/develop
# 4. 进行本地开发
# ... 代码编辑 ...
git add .
git commit -m "feat: add user authentication"
# 5. 推送到远程仓库
git push origin feature/auth
【代码注释】 :clone 默认只检出远程 default branch (多为 main),其它分支仅作 origin/xxx 远程跟踪引用。检出远程分支:git switch -c develop origin/develop。推送映射:git push origin 本地名:远程名;删除远程分支:git push origin --delete 分支名。
5.5 多人协作完整工作流
#mermaid-svg-f7OOYJyMpDPxIGHH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f7OOYJyMpDPxIGHH .error-icon{fill:#552222;}#mermaid-svg-f7OOYJyMpDPxIGHH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f7OOYJyMpDPxIGHH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f7OOYJyMpDPxIGHH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f7OOYJyMpDPxIGHH .marker.cross{stroke:#333333;}#mermaid-svg-f7OOYJyMpDPxIGHH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f7OOYJyMpDPxIGHH p{margin:0;}#mermaid-svg-f7OOYJyMpDPxIGHH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster-label text{fill:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster-label span{color:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster-label span p{background-color:transparent;}#mermaid-svg-f7OOYJyMpDPxIGHH .label text,#mermaid-svg-f7OOYJyMpDPxIGHH span{fill:#333;color:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH .node rect,#mermaid-svg-f7OOYJyMpDPxIGHH .node circle,#mermaid-svg-f7OOYJyMpDPxIGHH .node ellipse,#mermaid-svg-f7OOYJyMpDPxIGHH .node polygon,#mermaid-svg-f7OOYJyMpDPxIGHH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f7OOYJyMpDPxIGHH .rough-node .label text,#mermaid-svg-f7OOYJyMpDPxIGHH .node .label text,#mermaid-svg-f7OOYJyMpDPxIGHH .image-shape .label,#mermaid-svg-f7OOYJyMpDPxIGHH .icon-shape .label{text-anchor:middle;}#mermaid-svg-f7OOYJyMpDPxIGHH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f7OOYJyMpDPxIGHH .rough-node .label,#mermaid-svg-f7OOYJyMpDPxIGHH .node .label,#mermaid-svg-f7OOYJyMpDPxIGHH .image-shape .label,#mermaid-svg-f7OOYJyMpDPxIGHH .icon-shape .label{text-align:center;}#mermaid-svg-f7OOYJyMpDPxIGHH .node.clickable{cursor:pointer;}#mermaid-svg-f7OOYJyMpDPxIGHH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f7OOYJyMpDPxIGHH .arrowheadPath{fill:#333333;}#mermaid-svg-f7OOYJyMpDPxIGHH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f7OOYJyMpDPxIGHH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f7OOYJyMpDPxIGHH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f7OOYJyMpDPxIGHH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f7OOYJyMpDPxIGHH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f7OOYJyMpDPxIGHH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster text{fill:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH .cluster span{color:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f7OOYJyMpDPxIGHH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f7OOYJyMpDPxIGHH rect.text{fill:none;stroke-width:0;}#mermaid-svg-f7OOYJyMpDPxIGHH .icon-shape,#mermaid-svg-f7OOYJyMpDPxIGHH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f7OOYJyMpDPxIGHH .icon-shape p,#mermaid-svg-f7OOYJyMpDPxIGHH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f7OOYJyMpDPxIGHH .icon-shape .label rect,#mermaid-svg-f7OOYJyMpDPxIGHH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f7OOYJyMpDPxIGHH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f7OOYJyMpDPxIGHH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f7OOYJyMpDPxIGHH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有冲突
无冲突
早上上班
git pull拉取最新代码
本地开发修改代码
git add添加到暂存区
git commit提交到本地仓库
下班前推送到远程
git pull再次检查冲突
是否有冲突
解决冲突
git push推送
详细协作流程:
bash
# 【第一天】克隆 + 个人 feature 分支(勿直接在 main 上开发)
git clone https://github.com/team/project.git
cd project
git switch -c feature/user-profile # 从默认分支(main/master)分出
# 小步提交:每个逻辑块一次 commit,便于 review 与 bisect
git add src/components/UserProfile.js
git commit -m "feat: add user profile component"
git add src/styles/profile.css
git commit -m "style: profile component styling"
# 推送前先同步主线,减少 PR 时的大冲突
git pull origin main # 或 master;把主线合并进当前分支
git push -u origin feature/user-profile # -u 建立上游,之后可 git push
# 【日常】早 pull、晚 push;功能分支上开发,合并经 PR
git pull origin main # 每日开始先更新(或 git fetch + rebase origin/main)
git add .
git commit -m "feat: complete profile editing"
git pull origin main # 推送前再拉一次
git push # 已 -u 时可省略远程与分支名
【代码注释】 :协作铁律------先 pull(或 pull --rebase)再 push ,减少 non-fast-forward 拒绝。功能分支开发用 git pull origin main 或 git rebase origin/main 同步主线;推送前 git status 确认无未提交改动。冲突在本地解决后再 push,勿把带 <<<<<<< 的文件推上远程。
5.6 团队协作权限管理
5.6.1 GitHub组织管理
bash
# 【创建组织步骤】
# 1. GitHub页面操作
# 首页 -> 右上角+号 -> New Organization
# 选择免费计划(Free)
# 填写组织名称(避免使用中文)
# 填写联系邮箱
# 2. 邀请团队成员
# 组织首页 -> People -> Invite member
# 输入成员邮箱或GitHub用户名
# 3. 配置成员权限
# 组织首页 -> Settings -> Member privileges
# 权限级别:
# - Read: 只能读取,不能推送
# - Write: 可以推送代码
# - Admin: 完全管理权限
# 4. 创建团队仓库
# 组织首页 -> Repositories -> New repository
# 设置仓库可见性:Public/Private
【代码注释】 :组织、团队、仓库权限、分支保护规则均在 GitHub 网页 配置,Git CLI 无法替代。Read/Write/Admin 决定能否 push;保护分支可强制 PR + CI 通过才能合并,是生产主线的安全阀。
5.6.2 协作冲突解决
bash
# 【两人先后改同一文件并 push 到 main】理想情况:团队用 feature + PR,少直接推 main
# 开发者 A(先 push 成功)
git pull origin main
# 编辑 src/utils/helpers.js
git add src/utils/helpers.js
git commit -m "fix: update helper function"
git push origin main
# 开发者 B(本地基于旧 main 提交,push 时被拒)
git pull origin main # 本地已有 B 的 commit 时,pull 可能产生 merge commit
git add src/utils/helpers.js
git commit -m "feat: add new helper function"
git push origin main # ! [rejected] non-fast-forward → 须先整合远程
git pull origin main # 拉下 A 的提交并尝试自动合并
# 若同一区域都改了 → 冲突,文件中会出现 <<<<<<< / ======= / >>>>>>>
# 解决:删标记、合并逻辑、测试通过后
git add src/utils/helpers.js
git commit -m "merge: resolve conflicts in helpers.js with A's fix"
git push origin main
【代码注释】 :两人先后 push 同一分支时,后 push 者常需先 pull 合并。冲突时先沟通「保留哪侧业务逻辑」,避免盲目 --ours/--theirs 覆盖。合并提交 message 建议写明 merge: resolve helpers.js with @teammate 便于日后追溯。
5.7 SSH密钥免密登录配置
SSH密钥认证比HTTPS更安全且更便捷,适合频繁操作的开发者。
bash
# 【SSH 密钥配置完整流程】用密钥对替代 HTTPS 密码/Token,适合日常 push/pull
# 1. 生成密钥对(GitHub 现也推荐 ed25519;RSA 需 -b 4096)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 或:ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# -t:算法;-b:RSA 位长;-C:注释,常填邮箱便于在 GitHub 上辨认
# 2. 交互提示:保存路径(默认 ~/.ssh/id_ed25519)、私钥口令(建议设置)
# 3. 确认权限:私钥应为 600,.ssh 目录 700
ls -la ~/.ssh/
# id_ed25519 私钥 ------ 禁止上传、禁止进仓库、禁止发给他人
# id_ed25519.pub 公钥 ------ 整行粘贴到 GitHub → Settings → SSH keys
# 4. 复制公钥(必须完整一行,含开头的 ssh-ed25519 / ssh-rsa)
cat ~/.ssh/id_ed25519.pub
pbcopy < ~/.ssh/id_ed25519.pub # macOS;Linux 可用 xclip -sel clip < ...
# 5. GitHub 网页:Settings → SSH and GPG keys → New SSH key → Title + Key
# 6. 测试(成功:Hi <username>! You've successfully authenticated...)
ssh -T git@github.com
# 首次出现 host authenticity 提示时输入 yes,写入 known_hosts
# 7. 用 SSH URL 克隆(格式:git@github.com:用户/仓库.git)
git clone git@github.com:username/project.git
# 8. 已有 HTTPS 远程改为 SSH
git remote set-url origin git@github.com:username/project.git
git remote -v
【代码注释】 :认证原理是服务器用公钥 验签、本机用私钥 签名,私钥永不传上网。泄露私钥须立刻在 GitHub 删除对应 key 并重新 ssh-keygen。多账号可在 ~/.ssh/config 里为 github.com 指定 IdentityFile。ssh -T 只测登录身份,不代替仓库的 read/write 权限检查。
可运行示例 · SSH 配置步骤页:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SSH 免密登录 GitHub</title>
<style>body{font-family:system-ui;max-width:640px;margin:2rem auto;line-height:1.7} ol{padding-left:1.2rem} code{background:#f1f5f9;padding:2px 6px}</style>
</head>
<body>
<h1>SSH 密钥配置(5 步)</h1>
<ol>
<li>生成:<code>ssh-keygen -t rsa -C "your@email.com"</code></li>
<li>查看公钥:<code>cat ~/.ssh/id_rsa.pub</code></li>
<li>GitHub → Settings → SSH keys → New SSH key → 粘贴</li>
<li>测试:<code>ssh -T git@github.com</code></li>
<li>克隆:<code>git clone git@github.com:user/repo.git</code></li>
</ol>
</body>
</html>
【代码注释】 :macOS 用 pbcopy < ~/.ssh/id_ed25519.pub;Windows 路径 %USERPROFILE%\.ssh\。HTML 示例中的 id_rsa 可替换为 id_ed25519。WSL 与 Windows 双环境时注意 ~/.ssh/config 指定 IdentityFile,避免推代码时用错密钥。
SSH密钥工作原理:
GitHub服务器 本地Git 开发者 GitHub服务器 本地Git 开发者 #mermaid-svg-EveUIlJXryJFCn0A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EveUIlJXryJFCn0A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EveUIlJXryJFCn0A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EveUIlJXryJFCn0A .error-icon{fill:#552222;}#mermaid-svg-EveUIlJXryJFCn0A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EveUIlJXryJFCn0A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EveUIlJXryJFCn0A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EveUIlJXryJFCn0A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EveUIlJXryJFCn0A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EveUIlJXryJFCn0A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EveUIlJXryJFCn0A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EveUIlJXryJFCn0A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EveUIlJXryJFCn0A .marker.cross{stroke:#333333;}#mermaid-svg-EveUIlJXryJFCn0A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EveUIlJXryJFCn0A p{margin:0;}#mermaid-svg-EveUIlJXryJFCn0A .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EveUIlJXryJFCn0A text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-EveUIlJXryJFCn0A .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EveUIlJXryJFCn0A .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-EveUIlJXryJFCn0A .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-EveUIlJXryJFCn0A .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-EveUIlJXryJFCn0A #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-EveUIlJXryJFCn0A .sequenceNumber{fill:white;}#mermaid-svg-EveUIlJXryJFCn0A #sequencenumber{fill:#333;}#mermaid-svg-EveUIlJXryJFCn0A #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-EveUIlJXryJFCn0A .messageText{fill:#333;stroke:none;}#mermaid-svg-EveUIlJXryJFCn0A .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EveUIlJXryJFCn0A .labelText,#mermaid-svg-EveUIlJXryJFCn0A .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-EveUIlJXryJFCn0A .loopText,#mermaid-svg-EveUIlJXryJFCn0A .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-EveUIlJXryJFCn0A .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EveUIlJXryJFCn0A .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-EveUIlJXryJFCn0A .noteText,#mermaid-svg-EveUIlJXryJFCn0A .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-EveUIlJXryJFCn0A .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EveUIlJXryJFCn0A .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EveUIlJXryJFCn0A .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EveUIlJXryJFCn0A .actorPopupMenu{position:absolute;}#mermaid-svg-EveUIlJXryJFCn0A .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-EveUIlJXryJFCn0A .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EveUIlJXryJFCn0A .actor-man circle,#mermaid-svg-EveUIlJXryJFCn0A line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-EveUIlJXryJFCn0A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} git push命令 发送连接请求 发送随机挑战字符串 使用私钥签名挑战 发送签名的挑战 使用存储的公钥验证签名 认证成功,允许推送 显示推送成功
6. 企业级Git工作流
专业的Git工作流是团队高效协作的基础,不同的团队规模和项目类型适合不同的工作流。
6.1 GitFlow工作流详解
**【专业背景】**GitFlow由Vincent Driessen于2010年提出,是史上最成功的分支模型之一,特别适合有明确发布周期的项目。
6.1.1 核心分支说明
bash
# 【GitFlow分支类型详解】
# 1. master/main分支
# - 用途:生产环境代码,始终保持可发布状态
# - 约束:禁止直接在此分支开发
# - 保护:设置分支保护规则
# 2. develop分支
# - 用途:开发集成分支,包含最新开发功能
# - 来源:从master分支创建
# - 合并:功能分支合并到此,测试完成后合并到master
# 3. feature分支
# - 命名:feature/功能名称(如feature/user-auth)
# - 来源:从develop分支创建
# - 合并:完成后合并回develop
# - 生命周期:短期存在,功能完成后删除
# 4. release分支
# - 命名:release/版本号(如release/1.0.0)
# - 来源:从develop分支创建
# - 用途:发布准备,修复bug,更新版本号
# - 合并:完成后合并到develop和master
# 5. hotfix分支
# - 命名:hotfix/问题描述(如hotfix/critical-bug)
# - 来源:从master分支创建
# - 用途:紧急修复生产环境问题
# - 合并:完成后合并到master和develop
【代码注释】 :GitFlow 用分支类型 表达发布阶段:main 永远可部署,develop 集成日常功能,feature/* 短期,release/* 冻结版本只修 bug,hotfix/* 从 main 分支持紧急修复再双向合并。禁止在 main 直接开发可避免未测代码进入生产。
6.1.2 GitFlow 分支职责一览
| 分支 | 来源 | 合并目标 | 职责 |
|---|---|---|---|
| master/main | --- | --- | 生产可发布代码 |
| develop | master | master(经 release) | 日常集成 |
| feature/* | develop | develop | 功能开发 |
| release/* | develop | master + develop | 预发布测试 |
| hotfix/* | master | master + develop | 线上紧急修复 |
课堂资料中的 GitFlow 示意图(如
images/GitFlow.png)可与下表及流程图对照;无本地图片时以下 mermaid 即可。
6.1.3 GitFlow工作流程图
#mermaid-svg-W56Od9rSIF6V1ZAq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W56Od9rSIF6V1ZAq .error-icon{fill:#552222;}#mermaid-svg-W56Od9rSIF6V1ZAq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W56Od9rSIF6V1ZAq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W56Od9rSIF6V1ZAq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W56Od9rSIF6V1ZAq .marker.cross{stroke:#333333;}#mermaid-svg-W56Od9rSIF6V1ZAq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W56Od9rSIF6V1ZAq p{margin:0;}#mermaid-svg-W56Od9rSIF6V1ZAq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster-label text{fill:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster-label span{color:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster-label span p{background-color:transparent;}#mermaid-svg-W56Od9rSIF6V1ZAq .label text,#mermaid-svg-W56Od9rSIF6V1ZAq span{fill:#333;color:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq .node rect,#mermaid-svg-W56Od9rSIF6V1ZAq .node circle,#mermaid-svg-W56Od9rSIF6V1ZAq .node ellipse,#mermaid-svg-W56Od9rSIF6V1ZAq .node polygon,#mermaid-svg-W56Od9rSIF6V1ZAq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W56Od9rSIF6V1ZAq .rough-node .label text,#mermaid-svg-W56Od9rSIF6V1ZAq .node .label text,#mermaid-svg-W56Od9rSIF6V1ZAq .image-shape .label,#mermaid-svg-W56Od9rSIF6V1ZAq .icon-shape .label{text-anchor:middle;}#mermaid-svg-W56Od9rSIF6V1ZAq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W56Od9rSIF6V1ZAq .rough-node .label,#mermaid-svg-W56Od9rSIF6V1ZAq .node .label,#mermaid-svg-W56Od9rSIF6V1ZAq .image-shape .label,#mermaid-svg-W56Od9rSIF6V1ZAq .icon-shape .label{text-align:center;}#mermaid-svg-W56Od9rSIF6V1ZAq .node.clickable{cursor:pointer;}#mermaid-svg-W56Od9rSIF6V1ZAq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W56Od9rSIF6V1ZAq .arrowheadPath{fill:#333333;}#mermaid-svg-W56Od9rSIF6V1ZAq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W56Od9rSIF6V1ZAq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W56Od9rSIF6V1ZAq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W56Od9rSIF6V1ZAq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W56Od9rSIF6V1ZAq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W56Od9rSIF6V1ZAq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster text{fill:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq .cluster span{color:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W56Od9rSIF6V1ZAq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W56Od9rSIF6V1ZAq rect.text{fill:none;stroke-width:0;}#mermaid-svg-W56Od9rSIF6V1ZAq .icon-shape,#mermaid-svg-W56Od9rSIF6V1ZAq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W56Od9rSIF6V1ZAq .icon-shape p,#mermaid-svg-W56Od9rSIF6V1ZAq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W56Od9rSIF6V1ZAq .icon-shape .label rect,#mermaid-svg-W56Od9rSIF6V1ZAq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W56Od9rSIF6V1ZAq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W56Od9rSIF6V1ZAq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W56Od9rSIF6V1ZAq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建
创建功能分支
功能完成
发布准备
修复发布bug
发布完成
同步代码
紧急修复
修复完成
同步修复
master 生产分支
develop 开发分支
feature 新功能
合并到develop
release 发布分支
合并到master
hotfix 修复分支
6.1.4 GitFlow实际操作示例
bash
# 【GitFlow完整操作示例】
# ========== 1. 项目初始化 ==========
git init
git commit --allow-empty -m "Initial commit"
git switch -c develop
# ========== 2. 开发新功能 ==========
git switch -c feature/user-auth
# ... 开发用户认证功能 ...
git add .
git commit -m "feat: implement user authentication"
# 功能完成,合并到develop
git switch develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth
# ========== 3. 准备发布 ==========
git switch -c release/1.0.0
# 修复发现的bug,更新版本号
# ... 测试和修复 ...
vim package.json # 更新版本号为1.0.0
git add .
git commit -m "release: bump version to 1.0.0"
# 发布完成,合并到master和develop
git switch master
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "Release version 1.0.0"
git switch develop
git merge --no-ff release/1.0.0
git branch -d release/1.0.0
# ========== 4. 生产环境紧急修复 ==========
git switch master
git switch -c hotfix/critical-security-fix
# ... 修复安全漏洞 ...
git add .
git commit -m "hotfix: fix critical security vulnerability"
# 修复完成,合并到master和develop
git switch master
git merge --no-ff hotfix/critical-security-fix
git tag -a v1.0.1 -m "Hotfix version 1.0.1"
git switch develop
git merge --no-ff hotfix/critical-security-fix
git branch -d hotfix/critical-security-fix
【代码注释】 :示例中 merge --no-ff 保留功能/发布/热修的合并节点,便于按分支整体 revert。release 合并到 main 时打 tag -a 标记版本;hotfix 须同时合并 main 与 develop,避免修复只存在于生产线。小团队若发布频繁,可简化为 GitHub Flow 降低分支管理成本。
6.2 GitHub Flow工作流
**【专业背景】**GitHub Flow是2011年提出的简化版工作流,特别适合持续部署的云服务项目。
bash
# 【GitHub Flow核心特点】
# 1. 始终保持master分支可部署
# 2. 新功能从master分支创建feature分支
# 3. 功能完成后提交Pull Request
# 4. 代码Review通过后合并到master
# 5. 合并后立即部署
# ========== 操作示例 ==========
# 1. 从master创建功能分支
git switch master
git pull origin master
git switch -c feature/payment-integration
# 2. 开发功能
# ... 代码编辑 ...
git add .
git commit -m "feat: add payment gateway integration"
# 3. 推送到远程,创建Pull Request
git push origin feature/payment-integration
# 4. 在GitHub网页上创建PR,请求合并到master
# 5. 团队成员进行代码Review
# 6. 根据反馈修改代码
git add .
git commit -m "fix: address review comments"
git push origin feature/payment-integration
# 7. PR被批准后,通过GitHub网页合并到master
# 8. 删除功能分支
git switch master
git pull origin master
git branch -d feature/payment-integration
【代码注释】 :GitHub Flow 只有 main + 短期 feature/*,合并必经 PR + Review + CI 绿 。main 须随时可部署;步骤 3--7 在网页完成 PR,避免本地直接 merge 到 main。适合 Web/SaaS;不适合需长期并行维护 v1/v2 的客户端发版节奏。
6.3 GitLab Flow工作流
**【专业背景】**GitLab Flow结合了GitFlow的分支管理和GitHub Flow的简洁性,增加了环境分支的概念。
bash
# 【GitLab Flow工作流】
# 1. master分支:主开发分支
# 2. 环境分支:staging、production等
# 3. 功能分支:从master创建,合并回master
# ========== 操作示例 ==========
# 1. 开发新功能
git switch master
git switch -c feature/new-dashboard
# 2. 完成功能,合并到master
git switch master
git merge feature/new-dashboard
git push origin master
# 3. 部署到staging环境
git switch -c staging master
git push origin staging
# 4. staging测试通过后,部署到生产环境
git switch -c production staging
git push origin production
【代码注释】 :GitLab Flow 在 main 之上用 环境分支 (staging、production)表达部署阶段:代码沿 main → staging → production 晋升,每步对应一次部署与验收。与 GitFlow 比分支更少,但需规范谁可推进环境分支,防止配置漂移。示例中 git switch -c staging master 表示从 main 创建/更新预发分支。
6.4 Trunk-Based Development(主干开发)
【专业背景】Trunk-Based Development(TBD)是 Google、Facebook、Netflix 等大型科技公司广泛采用的开发模式,被《DevOps 加速》(DORA 研究报告)认定为高效能团队的核心实践之一。
核心理念
TBD 的核心是:所有开发者直接向主干(trunk/main)提交代码,或通过极短生命周期(≤1天)的功能分支集成,彻底避免长期分支带来的集成噩梦。
GitFlow(传统):
main ─────────────────────────────── 发布
develop ──────────────────────────── 集成
feature/A ──────── (2周) ─────────── 合并
feature/B ───── (3周) ──────────── 合并
↑ 越来越长的分支 = 越来越大的集成风险
Trunk-Based Development:
main ──●──●──●──●──●──●──●──●────── 持续部署
↑ ↑ ↑ ↑ ↑ ↑
小功能 小功能 小功能 小功能(每次提交均可部署)
关键实践与命令
bash
# ── 核心:功能分支生命周期 ≤ 1天,早分早合 ──
# 1. 每日从最新 main 开出 feature 分支
git switch main
git pull --rebase origin main # 始终从最新 main 分出
git switch -c feature/add-dark-mode # 命名简短,当天完成
# 2. 小步提交:每个逻辑单元一次 commit(目标:PR ≤ 400 行 diff)
git add src/theme/dark.css
git commit -m "feat: add dark mode CSS variables"
git add src/hooks/useTheme.js
git commit -m "feat: theme switch hook with localStorage persistence"
# 3. 提交 PR 前同步主干(避免 CI 失败)
git fetch origin
git rebase origin/main # 将本次改动「接」到最新 main 之后
# 4. PR 合并后立即删除分支(不留「积压」分支)
git push origin feature/add-dark-mode
# → PR Review → CI 绿灯 → Squash Merge 到 main → 自动部署
git push origin --delete feature/add-dark-mode
# ── Feature Flag(功能开关):未完成的大功能仍能持续集成 ──
# 大功能分多次提交,用开关隐藏「未就绪」的 UI
javascript
// Feature Flag 示例:功能代码已在 main,但默认关闭
// 不影响其他队友每天集成,CI 也不会因「半成品」页面失败
const FEATURE_FLAGS = {
darkMode: process.env.REACT_APP_DARK_MODE === 'true', // 环境变量控制
newCheckout: false, // 硬编码关闭,待 QA 通过后改为 true 再合并
};
function App() {
return (
<Router>
{FEATURE_FLAGS.darkMode && <ThemeProvider />}
{FEATURE_FLAGS.newCheckout ? <NewCheckout /> : <LegacyCheckout />}
</Router>
);
}
三大工作流横向对比
| 维度 | GitFlow | GitHub Flow | Trunk-Based Dev |
|---|---|---|---|
| 分支数量 | 多(5类) | 少(main + feature) | 最少(main + 短期 feature) |
| 集成频率 | 低(周/月级) | 中(天级) | 高(小时/天级) |
| 适合团队规模 | 大型,多版本并行 | 中型,SaaS | 大型,高频发布 |
| CI/CD 要求 | 一般 | 较高 | 极高(测试覆盖率高) |
| 学习曲线 | 较高 | 低 | 中(需配套 Feature Flag) |
| 代表企业 | 传统企业软件 | GitHub 自身 | Google、Netflix、Facebook |
【代码注释】 :TBD 的成功前提是高测试覆盖率 + 完善 CI ------主干随时可部署意味着每次提交都须通过自动化质量门禁。Feature Flag 解决了「代码已合并但功能未就绪」的矛盾,常与 LaunchDarkly、GrowthBook 等工具集成,实现灰度发布与 A/B 测试。小团队或需严格版本管理的客户端产品(App Store 审核周期长)更适合 GitHub Flow 或 GitFlow;大型后端服务/SaaS 选 TBD 可将平均 PR 合并时间从天缩短到小时。
7. Git高级技巧与最佳实践
7.1 高级命令技巧
bash
# 【Git 高级操作技巧】
# 1. 交互式暂存:按 hunk 审查,y/n/s/e 决定是否纳入本次 commit
git add -p # 适合「同一文件只提交部分改动」的精细提交
# 2. 交互式变基:整理最近 3 条提交(squash、reword、drop、调序)
git rebase -i HEAD~3 # 仅用于未推送或团队允许改写历史的分支
# 3. 储藏:把工作区+暂存区改动压入栈,换分支处理紧急任务
git stash # 等价于 stash push(默认不含 untracked)
git stash push -m "wip: login form half done"
git stash list # stash@{0} 为栈顶,越新数字越小
git stash pop # 应用栈顶并删除该条 stash
git stash apply stash@{1} # 应用但不删除,可重复套用同一储藏
# 4. 单文件全历史 diff(含 rename 追踪)
git log --follow --patch -- filename.txt
# 5. 在当前 HEAD 树中全文搜索(比 rg 快,只搜已提交版本)
git grep "function_name"
# 6. 二分定位引入 bug 的提交(见 7.3.1 详述)
git bisect start
git bisect bad # 当前检出版本有问题
git bisect good v1.0.0 # 已知正常的 tag 或 commit
# 循环:测试 → git bisect good|bad,直到打印 first bad commit
git bisect reset # 结束并回到 bisect 开始前分支
# 7. 撤销最后一次提交,改动保留在暂存区(可改 message 再提交)
git reset --soft HEAD~1
# 8. 修改最后一次提交(未 push 时);追加文件用 git add 后 --amend
git commit --amend -m "fix: correct message"
# 9. 空提交:不改变树,仅新增 commit 节点(触发 CI、占位)
git commit --allow-empty -m "ci: trigger pipeline"
【代码注释】 :add -p 是提交粒度控制的核心技能;rebase -i 与 reset 会改写历史,禁止 对已推送共享分支随意使用。stash 默认不储藏未跟踪新文件,需 --include-untracked。bisect 复杂度 O(log n),适合「不知何时引入」的回归。--amend 会改变最后一次 commit 的哈希,若已 push 需 push --force-with-lease(仅个人分支)。
7.1.1 git stash 进阶用法
stash 是开发过程中最常用的"救急"工具,把未完成的工作临时存起来,腾出干净的工作区处理紧急任务。
bash
# 基础储藏:保存已跟踪文件的修改 + 已暂存状态,工作区恢复为干净
git stash # 栈顶记为 stash@{0},可多次 stash 形成栈
# 带说明的储藏(多条 stash 时必备,否则 list 难以辨认)
git stash push -m "feat: half-done user profile form"
# 连同未跟踪(Untracked)新文件一起储藏
git stash push --include-untracked -m "wip: new feature files"
# 含被 .gitignore 忽略的文件(体积大、慎用,一般用于临时全量备份)
git stash push --all -m "full backup"
git stash list
# stash@{0}: On feature/login: feat: half-done user profile form ← 最新
# stash@{1}: On main: wip: quick experiment
git stash pop # apply 栈顶 + drop;冲突时 stash 仍保留
git stash apply stash@{1} # 只应用不删除,可对多分支重复套用同一储藏
git stash show -p stash@{0} # -p:查看该条 stash 相对 HEAD 的完整 patch
# 基于 stash 创建分支并应用(避免 pop 到错误分支;成功后自动 drop 该 stash)
git stash branch feature/saved-work stash@{0}
git stash drop stash@{1} # 删除单条
git stash clear # 清空整个 stash 栈,不可恢复
经典场景一:紧急 Bug 打断开发
bash
# 正在开发新功能,突然收到线上 P0 Bug 通知
git stash push -m "wip: add user avatar upload (50% done)"
# 切到 main,创建 hotfix 分支
git switch main
git switch -c hotfix/login-crash
# ... 修复 bug ...
git commit -m "fix: prevent null pointer on login"
git switch main
git merge hotfix/login-crash
git push origin main
# Bug 修完,恢复原来的工作
git switch feature/user-profile
git stash pop
# 继续开发头像上传功能
经典场景二:发现 stash 在错误分支上,转移到正确分支
bash
# 发现 stash@{0} 的代码应该在 feature/order 分支上工作
git stash branch feature/order stash@{0}
# Git 自动:切换到 feature/order → apply stash → drop stash
【代码注释】 :stash 存的是工作区+暂存区 快照,不是 commit;切换分支前 git status 不干净时优先 stash。pop = apply + drop;若 apply/pop 产生冲突,stash 条目仍保留 ,解决冲突后需手动 drop。stash branch 能在新分支上应用储藏,避免污染当前分支。储藏过久可能因底层文件变更导致冲突,宜尽早 pop。
7.2 Git钩子(Hooks)配置
Git钩子是在特定事件发生时自动执行的脚本,可以用于代码质量检查、自动部署等。
bash
# 【Git Hooks配置示例】
# 1. 客户端钩子目录
cd .git/hooks/
# 2. 创建pre-commit钩子(提交前检查)
cat > pre-commit << 'EOF'
#!/bin/bash
# 运行代码检查
npm run lint
# 如果检查失败,阻止提交
if [ $? -ne 0 ]; then
echo "代码检查失败,请修复后再提交"
exit 1
fi
EOF
chmod +x pre-commit
# 3. 创建commit-msg钩子(检查提交信息)
cat > commit-msg << 'EOF'
#!/bin/bash
# 检查提交信息格式
commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}'
if ! grep -qE "$commit_regex" $1; then
echo "提交信息格式错误,请使用约定式提交格式"
echo "示例: feat: add user login"
exit 1
fi
EOF
chmod +x commit-msg
# 4. 创建post-merge钩子(合并后操作)
cat > post-merge << 'EOF'
#!/bin/bash
# 合并后自动安装依赖
if [ -f package.json ]; then
npm install
fi
EOF
chmod +x post-merge
【代码注释】 :Hooks 位于 .git/hooks/,文件名固定(如 pre-commit),chmod +x 后生效;exit 1 阻断后续 Git 操作。pre-commit 跑 lint、commit-msg 校验 Conventional Commits、post-merge 在 pull 后装依赖。Hooks 默认不随仓库共享 ,团队常用 Husky + lint-staged 将钩子配置写入项目并提交。
7.3 Git别名配置
bash
# 【Git别名配置】
# 1. 常用别名设置
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.unstage 'reset HEAD --'
git config --global alias.last 'log -1 HEAD'
git config --global alias.visual 'log --graph --oneline --all'
# 2. 复杂别名
git config --global alias.lg "log --graph --oneline --all --decorate"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.undo "reset --soft HEAD~1"
# 3. 使用别名
git co main # 等同于 git checkout main
git br # 等同于 git branch
git ci -m "message" # 等同于 git commit -m "message"
# 4. 查看所有别名
git config --global --get-regexp alias
【代码注释】 :别名存于 ~/.gitconfig 的 [alias],整条命令用引号包裹(如 lg)。unstage 映射 reset HEAD -- 在 Git 2.23+ 可改为 restore --staged。勿用与内置子命令冲突的短名;团队文档应列出别名表。undo = reset --soft HEAD~1 仅适用于未推送的最后一次提交。
7.3.1 git bisect:二分法快速定位引入 Bug 的提交
当你不知道哪次提交引入了 Bug,bisect 用二分查找帮你在 O(log n) 次内锁定"罪犯提交"。
bash
# 1. 进入二分模式(会 detached 或移动 HEAD 到中间提交,结束前勿在 bisect 中开发)
git bisect start
# 2. 标记当前检出为「有问题」
git bisect bad
# 3. 标记已知正常的锚点(tag、release 分支或旧 commit)
git bisect good v1.0.0
# Git 检出 good 与 bad 之间的中间 commit,提示约剩 N 步
# 4. 在当前中间版本上跑测试/复现 bug,再标记结果
npm test # 或手动验证
git bisect bad # 仍有 bug → 问题在更早 half
# 或 git bisect good # 正常 → 问题在更晚 half
# 5. 重复测试与 good/bad,直到:
# abc1234 is the first bad commit
# Author / Date / Summary ...
# 6. 结束并回到 bisect start 前的分支位置
git bisect reset
# 全自动:由脚本判定 good(0) / bad(非0),无需人工反复 checkout
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run npm test # package.json 中 test 须能反映该 bug 是否存在
【代码注释】 :bisect 在 good, bad 闭区间内二分,复杂度约 log₂(N) 次测试。good 必须确实无 bug,bad 必须有 bug,否则结果无意义。过程中 HEAD 会反复跳转,应用 git bisect reset 归位。git bisect run 要求测试脚本 exit 0/非0 稳定对应 good/bad。找到 commit 后可用 git show abc1234 看引入改动的文件列表。
7.3.2 git worktree:同时检出多个分支
worktree 让你无需切换分支就能在另一个目录打开同一仓库的不同分支,适合同时处理多个版本。
bash
# 在同一仓库再挂一个工作目录,检出另一分支(共享 .git,对象库不重复)
git worktree add ../project-hotfix hotfix/login-crash
# 路径 ../project-hotfix 为新建目录;第二参数为分支名(不存在时可 -b 新建)
cd ../project-hotfix
git log --oneline -3
# 在此目录修复、commit;主目录仍停留在原分支,互不切换
cd ../project-main # 主 worktree 目录
git worktree list # 显示各 worktree 路径、HEAD、分支
git worktree remove ../project-hotfix # 解除关联;目录若仍有文件需先清空或加 --force
【代码注释】 :worktree 解决「同一仓库不能同时 checkout 两个分支」的限制,适合 v1 热修 + v2 并行开发、或同时跑两个 npm start(不同端口)。与 stash 区别:stash 隐藏改动;worktree 是真实第二工作区 。注意同一分支不能被两个 worktree 同时检出。删除用 git worktree remove;遗留目录可 git worktree prune 清理失效注册。
7.4 git blame:代码考古与责任追溯
git blame 是代码审查与 Bug 调试的利器------它为文件的每一行标注「最后是谁、在哪次提交中修改了它」。
bash
# 基础用法:为每一行打上 commit 哈希、作者、时间
git blame src/utils/helpers.js
# 输出格式示例:
# a1b2c3d4 (张三 2024-03-15 10:23:41 +0800 42) export function formatDate(date) {
# e5f6g7h8 (李四 2024-05-22 14:07:12 +0800 43) return date.toLocaleDateString('zh-CN');
# a1b2c3d4 (张三 2024-03-15 10:23:41 +0800 44) }
# 仅查看指定行范围(大文件时避免刷屏)
git blame -L 40,60 src/utils/helpers.js # -L:行号范围
git blame -L /formatDate/,+10 src/utils/helpers.js # 从匹配行开始的 10 行
# 忽略空白字符改动(格式化工具引起的行改动不影响追溯真实改动者)
git blame -w src/utils/helpers.js
# 跨文件追踪:-C 检测从其他文件复制过来的代码(代码移动后仍显示原始作者)
git blame -C -C src/utils/helpers.js
# 查看某次历史提交时该文件的状态(时光机:「这行 2023 年是谁写的」)
git blame abc1234 -- src/utils/helpers.js
经典场景一:发现 Bug,定位责任提交
bash
# 步骤1:找到 bug 所在行
# 假设 calculateDiscount 函数第 58 行有问题
git blame -L 55,62 src/pricing/discount.js
# 输出:
# 3f4e5d6c (王五 2024-12-01 09:15:33 +0800 58) if (user.vip && discount > 0.5) {
# 步骤2:查看这次提交的完整改动
git show 3f4e5d6c
# 步骤3:理解业务背景(查看提交关联的 PR/Issue)
git log 3f4e5d6c -1 --format="%H %s %b"
# 通常提交说明里包含 "closes #123" 或 "JIRA-456" 等关联信息
经典场景二:Code Review 中快速了解代码历史
bash
# 为什么这里有个魔法数字 0.85?blame 一下
git blame -L 30,35 src/api/payment.js
# abc1234 (赵六 2024-09-10 ...) const TAX_RATE = 0.85;
# 查看提交说明,找到业务原因
git show abc1234 --stat
# "fix: apply correct VAT rate for EU customers (closes #892)"
# 原来是增值税率,不是魔法数字 ------ 加个注释说明
【代码注释】 :git blame 显示的是「最后一次修改该行 的提交」,不是「写出 bug 的人」------可能某次格式化整文件导致所有行都指向同一提交。-w 忽略空白可避开这类噪音;-C -C 可跨文件追踪代码移动。VS Code 安装 GitLens 插件后,鼠标悬浮即可看到 blame 信息,无需命令行。
git blame 与 git log 组合使用(最强代码考古流程):
bash
# 1. blame 找到「最后改过此行」的 commit
git blame -L 100,105 src/checkout/cart.js
# xyz9876 (某人 2025-01-15) const MAX_ITEMS = 99;
# 2. 查看那次 commit 改了哪些文件
git show --stat xyz9876
# 3. 再往前追:这行最初是哪次 commit 引入的?(按 pickaxe 搜索)
git log -S "MAX_ITEMS = 99" --oneline -- src/
# 4. 查看完整变更轨迹(含 rename)
git log --follow --patch -S "MAX_ITEMS" -- src/checkout/cart.js
7.5 git submodule:管理项目中的子模块
git submodule 允许一个 Git 仓库引用另一个仓库(子模块)的特定提交,常用于管理共享组件库、第三方依赖或微前端架构中的子项目。
bash
# ── 添加子模块 ──
# 将外部仓库引入为当前项目的子模块(锁定到某个具体 commit)
git submodule add https://github.com/org/ui-library.git packages/ui
# 此操作:
# 1. 克隆 ui-library 到 packages/ui/
# 2. 创建 .gitmodules 记录子模块配置
# 3. 在父仓库中记录「引用 ui-library 的哪个 commit」
# 查看 .gitmodules 内容(子模块配置清单)
cat .gitmodules
# [submodule "packages/ui"]
# path = packages/ui
# url = https://github.com/org/ui-library.git
# ── 克隆含子模块的仓库(两步走)──
# 方式1:克隆时自动初始化(推荐)
git clone --recurse-submodules https://github.com/org/main-project.git
# 方式2:已克隆但未初始化(常见问题:packages/ui 目录为空)
git submodule init # 读取 .gitmodules,注册子模块
git submodule update # 实际克隆子模块并 checkout 到记录的 commit
# 合并一步:
git submodule update --init --recursive # --recursive 处理嵌套子模块
# ── 更新子模块到最新版本 ──
# 进入子模块目录,像普通仓库一样操作
cd packages/ui
git fetch origin
git switch main
git pull
# 回到父仓库,提交「引用了子模块的新 commit」这一变更
cd ../..
git add packages/ui # 父仓库的改动是「子模块指针从旧 hash → 新 hash」
git commit -m "chore: upgrade ui-library to v2.1.0"
# 或者批量更新所有子模块到各自远程最新
git submodule update --remote --merge
# ── 查看子模块状态 ──
git submodule status
# abc1234 packages/ui (v2.0.0-5-gabc1234)
# ↑ 哈希前无标记=正常;+ 表示子模块有新改动待提交;- 表示未初始化
# ── 删除子模块(三步操作)──
git submodule deinit -f packages/ui # 从 .git/config 中注销
git rm -f packages/ui # 从工作区和 .gitmodules 移除
rm -rf .git/modules/packages/ui # 清理残留的对象库
git commit -m "chore: remove ui submodule"
经典场景:monorepo 与子模块选型
场景 推荐方案 理由
─────────────────────────────────────────────────────────────
共享 UI 组件库 git submodule 组件库有独立版本、独立 CI、独立发布
第三方只读依赖 npm/yarn 包管理器更适合,submodule 过重
微前端子应用 git submodule 各子应用团队独立仓库,父仓库锁定版本
同一团队多包 monorepo (pnpm) 无需版本锁定,统一构建更高效
【代码注释】 :子模块的核心是「父仓库只存子模块的 commit 哈希 」,不存子模块的代码------这意味着 git clone 父仓库后子目录默认为空,须 submodule update --init 填充。子模块版本升级须在父仓库提交一次「更新子模块指针」的 commit,这让版本管理非常清晰但也是子模块最容易让新手困惑的地方。与 npm 包相比,submodule 适合「需要访问源码、可能需要 hotfix 子模块再同步回来」的场景;纯依赖首选包管理器。
7.6 团队协作最佳实践
bash
# 【团队Git协作规范】
# 1. 提交信息规范(约定式提交)
feat: 新功能
fix: 修复bug
docs: 文档修改
style: 代码格式调整
refactor: 重构代码
test: 测试相关
chore: 构建/工具链相关
# 示例:
git commit -m "feat: add user authentication"
git commit -m "fix: resolve login timeout issue"
git commit -m "docs: update API documentation"
# 2. 分支命名规范
feature/功能描述
bugfix/问题描述
hotfix/紧急修复
release/版本号
dev/开发者姓名
# 示例:
git switch -c feature/user-profile
git switch -c bugfix/login-error
git switch -c hotfix/security-patch
# 3. 代码审查流程
# - 创建Pull Request
# - 填写PR模板(功能描述、测试情况、相关Issue)
# - 至少一人审查通过
# - 通过CI检查
# - 合并到主分支
# 4. 分支保护规则
# - master/main分支设为保护分支
# - 要求Pull Request审查
# - 要求状态检查通过
# - 限制推送权限
【代码注释】 :约定式提交(feat:/fix:/chore:)便于自动生成 CHANGELOG 与语义化版本。分支名与 Issue 编号对应(如 feature/123-login)可追溯需求。保护 main + 必过 CI + 至少一人 Approve,比线上 revert 成本低。PR 模板应要求:测了什么、影响范围、回滚方案。
8. 故障排除与问题解决
8.1 常见问题解决方案
bash
# 【Git常见问题及解决方案】
# 1. 忘记提交敏感信息
# 问题:不小心提交了密码、API密钥等敏感信息
# 解决:
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch config/secrets.js" \
--prune-empty --tag-name-filter cat -- --all
# 2. 误删分支恢复
# 问题:不小心删除了重要分支
# 解决:
git reflog # 查找分支的最后一个提交
git switch -c recovered-branch abc123def # 重建分支
# 3. 合并错误回滚
# 问题:错误的合并操作
# 解决:
git reset --hard HEAD~1 # 回滚合并提交
git reset --hard ORIG_HEAD # 回滚到合并前状态
# 4. 文件冲突标记残留
# 问题:解决冲突后仍有冲突标记
# 解决:
git checkout --ours filename # 使用当前分支版本
git checkout --theirs filename # 使用合并分支版本
git checkout --merge filename # 尝试自动合并
# 5. 推送被拒绝
# 问题:本地版本落后于远程
# 解决:
git pull --rebase origin master
git push origin master
# 6. 忽略规则不生效
# 问题:.gitignore规则无法忽略已跟踪文件
# 解决:
git rm --cached filename
git add .
git commit -m "fix: update gitignore"
【代码注释】 :排错顺序:git status → git log --oneline -5 → git reflog。敏感信息进库须轮换密钥 + git filter-repo/BFG,不只 rm --cached。ORIG_HEAD 在 merge/pull 后保存操作前 HEAD,误合并可 reset --hard ORIG_HEAD(未再移动 HEAD 时)。filter-branch 已过时,新项目优先 filter-repo。推送被拒先 pull --rebase,忌盲目 --force 到共享分支。
8.1.1 高频事故场景速查
场景 A:commit message 写错了(尚未推送)
bash
# 修改最后一次提交的 message
git commit --amend -m "fix: correct typo in login validation"
# 修改最后一次提交的内容(追加文件)
git add forgotten-file.js
git commit --amend --no-edit # 保留原 message,追加文件进上次提交
场景 B:git add . 误暂存了不该提交的文件
bash
# 取消暂存某个文件(保留工作区改动)
git restore --staged .env
git restore --staged dist/
# 查看暂存区内容再决定
git diff --cached --name-only
场景 C:git push 后发现推错了分支
bash
# 情况1:推到了远程 feature 分支(可以强制撤回)
git push origin feature/wrong-branch --delete # 删除远程分支
# 或者
git revert <last-commit>
git push
# 情况2:不小心推到了 main(高风险!)
# 先联系团队,告知大家暂停 pull
git revert HEAD # 撤销那次提交
git push origin main # 推送撤销提交
# 通知团队重新 pull
场景 D:本地和远程分支历史分叉(非线性)
bash
# 错误信息:! [rejected] main -> main (non-fast-forward)
# 原因:本地有远程没有的提交,同时远程也有本地没有的提交
# 方案1:合并(保留两边历史)
git pull origin main # 产生合并提交
# 方案2:变基(线性历史,推荐个人分支使用)
git pull --rebase origin main # 相当于 fetch + rebase
git push origin main
场景 E:误执行 git reset --hard 丢失了未提交改动
bash
# 已提交的内容可用 reflog 找回
git reflog
# 找到丢失前的 commit hash
# 未提交且未 stash 的工作区改动:通常无法恢复(Git 没有记录过)
# 预防措施:频繁 commit 或使用 git stash
场景 F:git merge 后悔了,想撤销合并
bash
# 合并后尚未推送
git reset --hard ORIG_HEAD # ORIG_HEAD 保存了合并前的状态
# 合并后已推送(安全方式)
git revert -m 1 <merge-commit-hash>
git push origin main
场景 G:大文件被误提交,仓库体积暴增
bash
# 找出大文件
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sort -k3 -rn | head -20
# 从历史中彻底删除(会重写所有历史,谨慎!)
# 推荐使用 BFG Repo Cleaner(比 filter-branch 快 10-720 倍)
# brew install bfg
bfg --delete-files "*.zip" my-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
8.2 性能优化建议
bash
# 【Git性能优化技巧】
# 1. 大文件处理
# 问题:仓库中存在大文件导致克隆缓慢
# 解决:使用Git LFS(Large File Storage)
git lfs install
git lfs track "*.psd"
git lfs track "*.zip"
git add .gitattributes
git commit -m "feat: enable git lfs"
# 2. 浅克隆
# 问题:只需要最新代码,不需要完整历史
# 解决:
git clone --depth 1 https://github.com/user/repo.git
git clone --depth 1 --no-single-branch https://github.com/user/repo.git
# 3. 稀疏检出
# 问题:只需要仓库的部分文件
# 解决:
git clone --no-checkout https://github.com/user/repo.git
cd repo
git sparse-checkout init
git sparse-checkout set src/lib
git checkout
# 4. 垃圾回收
# 问题:仓库体积过大
# 解决:
git gc --prune=now
git gc --aggressive --prune=now
# 5. 清理未跟踪文件
git clean -fd # 删除未跟踪文件和目录
git clean -fdX # 包括通常被忽略的文件
【代码注释】 :大二进制用 Git LFS 或勿入库;node_modules/dist 必须进 .gitignore。克隆慢用 --depth 1 浅克隆;monorepo 用 sparse-checkout 只检出子目录。git gc --aggressive 在维护窗口执行,会占 CPU。git clean -fd 删除未跟踪 文件,执行前确认无未备份草稿;-fdX 会删被 ignore 的文件,更危险。
9. 总结与展望
Git作为现代软件开发的基石工具,其重要性不言而喻。通过本文的系统学习,我们从基础命令到高级技巧,从个人开发到团队协作,全面掌握了Git的使用方法。
9.1 核心知识点回顾
- 基础与配置:init / add / commit / status / diff / restore;config 三层优先级(system/global/local)
- 版本管理 :git log(含
-SPickaxe 考古、--grep过滤)、reflog、reset 三模式、revert 安全撤销、tag 语义化版本 - 忽略机制 :.gitignore 语法精讲、
git rm --cached、check-ignore调试 - 分支策略:branch / switch / merge;rebase 变基;cherry-pick 摘取;Detached HEAD 处理;冲突解决
- 远程协作 :
git fetchvsgit pull核心区别;remote 配置;SSH 免密;submodule 子模块管理 - 工作流程 :GitFlow(多版本发布)、GitHub Flow(SaaS 持续部署)、GitLab Flow(环境分支)、Trunk-Based Development(高频集成,Google/Netflix 实践)
- 高级技巧 :stash 储藏、bisect 二分调试、worktree 多工作区、blame 代码考古、Hooks 质量门禁、性能优化
9.2 实践建议
- 建立团队规范:制定明确的分支管理、提交信息、代码审查规范
- 保护重要分支:设置master/main分支保护规则
- 定期备份:重要项目定期推送到多个远程仓库
- 持续学习:关注Git新特性和工具改进
9.3 学习资源
- 官方文档:https://git-scm.com/doc
- GitHub指南:https://guides.github.com/
- 在线练习:https://learngitbranching.js.org/
- 可视化学习:https://onlywei.github.io/explain-git-with-d3/
【专业建议】:掌握Git需要理论学习和大量实践相结合,建议在实际项目中逐步应用这些知识,遇到问题时查阅官方文档或社区资源。
Git不仅仅是一个工具,更是一种协作思维。良好的Git使用习惯将极大地提升团队开发效率和代码质量。继续学习和实践,你一定能成为Git高手!
附录 A:命令与场景速查
A.1 日常高频操作
| 我想... | 命令 |
|---|---|
| 看改了什么(未 add) | git diff |
| 看改了什么(已 add) | git diff --cached |
| 撤销未 add 的改动 | git restore <file> |
| 撤销已 add(退出暂存区) | git restore --staged <file> |
| 快速提交所有已跟踪文件 | git commit -am "message" |
| 改最后一次提交说明 | git commit --amend -m "new msg" |
| 追加文件到上次提交 | git add <file> && git commit --amend --no-edit |
| 看简洁历史 | git log --oneline |
| 看分支拓扑 | git log --graph --oneline --all |
| 找丢掉的提交 | git reflog |
| 回退版本(本地) | git reset --hard <id> |
| 安全撤销已推送提交 | git revert <hash> |
A.2 分支操作
| 我想... | 命令 |
|---|---|
| 新建并切分支 | git switch -c <name> |
| 切换分支 | git switch <name> |
| 查看所有分支 | git branch -a |
| 合并分支 | git merge <name>(先切到目标分支) |
| 变基(线性历史) | git rebase main(在 feature 上执行) |
| 摘取指定提交 | git cherry-pick <hash> |
| 删除已合并分支 | git branch -d <name> |
| 删除远程分支 | git push origin --delete <name> |
| 检出标签快照 | git switch --detach v1.0.0 |
A.3 远程协作
| 我想... | 命令 |
|---|---|
| 第一次推送建立跟踪 | git push -u origin main |
| 安全拉取(推荐) | git fetch origin && git rebase origin/main |
| 快速拉取 | git pull --rebase |
| 克隆仓库(含子模块) | git clone --recurse-submodules <url> |
| 查看远程信息 | git remote -v / git remote show origin |
| 切换远程 URL | git remote set-url origin <new-url> |
A.4 文件与历史管理
| 我想... | 命令 |
|---|---|
| 忽略已跟踪文件 | git rm --cached <file> + .gitignore |
| 临时储藏工作 | git stash push -m "说明" |
| 恢复储藏 | git stash pop |
| 查看某行是谁写的 | git blame -L <start>,<end> <file> |
| 找引入某字符串的提交 | git log -S "关键字" --oneline |
| 二分找 bug 提交 | git bisect start && git bisect bad && git bisect good <tag> |
| 同时检出两分支 | git worktree add <path> <branch> |
| 打发布标签 | git tag -a v1.0.0 -m "Release" |
| 推送标签 | git push origin --tags |
| 初始化子模块 | git submodule update --init --recursive |
| 规则是否生效 | git check-ignore -v <path> |
| 查看 Git 对象内容 | git cat-file -p HEAD |
附录 B:官方与延伸阅读
| 资源 | 链接 | 说明 |
|---|---|---|
| Pro Git 中文版 | https://git-scm.com/book/zh/v2 | 系统学习首选 |
| Git 命令大全 | https://git-scm.com/docs | 官方命令参考 |
| GitHub Docs | https://docs.github.com/zh | PR、SSH、组织权限 |
| Learn Git Branching | https://learngitbranching.js.org/?locale=zh_CN | 可视化分支练习 |
| D3 Git 说明 | https://onlywei.github.io/explain-git-with-d3/ | 图形化理解内部结构 |
可视化工具(业界常用):
- SourceTree / Fork:桌面 GUI
- VS Code Git 面板:日常 diff、冲突解决
- GitKraken:图形化历史与合并
本文涵盖 Git 核心概念、课堂全流程(初始化 → 回滚 → 忽略 → 分支 → 远程 → GitFlow)及企业协作实践;文中 HTML 为命令练习清单,请在终端配合空仓库实操。所有 md 与课堂案例仓库均保留,可直接在案例目录执行 git log --graph 观察真实提交历史。