Git 作为一个分布式版本控制系统,允许你离线工作并在本地保存完整历史。本节将查看实现这一目的的基本组成部分。在你开始使用 Git 命令之前,掌握这些核心要点是必要的。它们是仓库、提交和分支。Git仓库:你项目的数据库每个由 Git 管理的项目中心都包含仓库,常简称为“repo”。把仓库看作 Git 为你项目建立的私有数据库。它存储了所有变更的完整历史、这些变更的元数据(例如谁在何时进行了变更),以及帮助整理此历史的指针(分支)。当你在项目目录中初始化 Git(使用我们很快会提到的命令 git init)时,Git 会创建一个特殊的隐藏子目录,名为 .git。digraph G { rankdir=LR; node [shape=folder, style=filled, fillcolor="#e9ecef", fontname="Arial"]; edge [arrowhead=none]; ProjectDir [label="我的项目/"]; GitDir [label=".git/ (仓库数据)", fillcolor="#ced4da"]; OtherFiles [label="你的文件.* 其他文件夹/"]; ProjectDir -> GitDir; ProjectDir -> OtherFiles; }Git 初始化后的典型项目目录结构,展示了隐藏的 .git 子目录以及你的项目文件。这个 .git 目录包含了 Git 跟踪项目所需的一切。它包括配置文件、日志、实际历史数据(高效存储),以及分支和标签的信息。因为整个仓库都本地存储在你的项目文件夹中,所以查看历史、比较版本或创建分支等操作都非常快,因为它们不需要网络访问。这种自包含的特性是 Git 分布式设计的一个直接优势。提交:你项目的快照如果仓库是数据库,那么提交是构成项目历史的独立记录。一个提交代表了你的整个项目在特定时间点上被跟踪文件的快照。它不仅是自上次提交以来发生变化的记录;它是 Git 在提交时所了解的项目状态的完整视图。每个提交都由一个唯一的加密哈希标识(具体来说,是一个 SHA-1 哈希值,看起来像 a1b2c3d4e5f6...)。此哈希值根据快照中文件的内容和与提交相关的元数据生成。每个提交中存储的组成部分包括:快照:引用了当时所有被跟踪文件和目录的状态。父提交:引用了紧接其之前的提交。这种提交的链接形成了项目的历史链。大多数提交有一个父提交,但合并提交(合并不同工作线)可以有多个父提交。仓库中的第一个提交没有父提交。元数据:关于谁创建了提交(作者姓名和电子邮件)、何时创建(时间戳)的信息,以及通常独立的关于谁应用了提交的信息(提交者姓名、电子邮件和时间戳)。提交消息:由作者编写的易于阅读的消息,描述了该快照中所做的更改。编写良好的提交消息对于理解项目的演变很重要。digraph G { rankdir=LR; node [shape=record, style=filled, fillcolor="#a5d8ff", fontname="Arial"]; edge [color="#495057"]; C3 [label="{提交 C3 (SHA: 7f8g9h...)|时间戳: ...\n作者: ...\n消息: '修复登录bug'|父提交: C2}"]; C2 [label="{提交 C2 (SHA: 4d5e6f...)|时间戳: ...\n作者: ...\n消息: '添加用户登录'|父提交: C1}"]; C1 [label="{提交 C1 (SHA: 1a2b3c...)|时间戳: ...\n作者: ...\n消息: '项目初始设置'|父提交: 无}"]; C3 -> C2; C2 -> C1; }提交历史的简化视图。每个提交都指回其父提交,形成一个代表项目时间线的链。将提交想象成在你开始新任务之前给你的办公桌拍一张照片。你捕捉了所有相关物品的状态,这样你就可以随时回顾那一刻的一切情况。分支:管理开发线Git 中的分支只是一个轻量级、可移动的指针,指向一个特定的提交。把它想象成附加到你历史中某个快照(提交)上的标签或书签。分支对 Git 工作流很重要,它允许你偏离主开发线,独立地处理不同的功能或修复,而互不影响。默认情况下,Git 仓库中的主开发线通常称为 main(尽管旧仓库可能使用 master 名称)。当你开始进行提交时,main 分支指针会自动向前移动,指向你在这条线上所做的最新提交。digraph G { rankdir=LR; node [shape=record, style=filled, fillcolor="#a5d8ff", fontname="Arial"]; branch [shape=plaintext, fontname="Arial", fontsize=10]; edge [color="#495057"]; subgraph cluster_0 { label = "提交历史"; bgcolor="#e9ecef"; C1 [label="{提交 C1}"]; C2 [label="{提交 C2}"]; C3 [label="{提交 C3}"]; C4 [label="{提交 C4}", fillcolor="#b2f2bb"]; // 功能分支上的提交 C1 -> C2 -> C3; } main_ptr [label="main", fillcolor="#96f2d7", style=filled, shape=box, fontcolor="#000"]; main_ptr -> C3; head_ptr [label="HEAD", fillcolor="#ffec99", style=filled, shape=box, fontcolor="#000"]; head_ptr -> main_ptr [style=dashed, arrowhead=open, constraint=false]; }main 分支指向最新提交 (C3)。HEAD 表示 main 是当前活跃的分支。如果你想处理一个新功能,你可以创建一个新分支,比如叫 new-feature。最初,这个新分支指针将指向与 main 相同的提交。然而,当你“在” new-feature 分支上进行新的提交时,只有 new-feature 指针会向前移动。main 分支保持不变,直到你决定将你的新功能合并回它。digraph G { rankdir=LR; node [shape=record, style=filled, fillcolor="#a5d8ff", fontname="Arial"]; branch [shape=plaintext, fontname="Arial", fontsize=10]; edge [color="#495057"]; subgraph cluster_0 { label = "提交历史"; bgcolor="#e9ecef"; C1 [label="{提交 C1}"]; C2 [label="{提交 C2}"]; C3 [label="{提交 C3}"]; C4 [label="{提交 C4}", fillcolor="#b2f2bb"]; // 功能分支上的提交 C1 -> C2; C2 -> C3; // 主线 C2 -> C4; // 功能线从 C2 分叉 } main_ptr [label="main", fillcolor="#96f2d7", style=filled, shape=box, fontcolor="#000"]; main_ptr -> C3; feature_ptr [label="new-feature", fillcolor="#ffec99", style=filled, shape=box, fontcolor="#000"]; feature_ptr -> C4; head_ptr [label="HEAD", fillcolor="#ffec99", style=filled, shape=box, fontcolor="#000"]; head_ptr -> feature_ptr [style=dashed, arrowhead=open, constraint=false]; }一个新的 new-feature 分支从提交 C2 创建。随后的提交 C4 只存在于此分支上。HEAD 现在指向 new-feature,表示它是当前活跃的分支。Git 还使用一个特殊的指针,称为 HEAD。HEAD 通常指向你当前正在工作的本地分支。当你切换分支时,HEAD 会更新以指向新分支。这告诉 Git 下一个提交应该添加到历史图的哪个位置。因为 Git 中的分支只是简单的指针(存储为包含它们所指向提交的 SHA-1 哈希值的文件),所以创建、切换和删除分支都非常快速高效。这种轻量级的分支模型是 Git 的一个重要优势,鼓励在任何大小的任务中频繁使用分支的工作流。这三个要点:仓库、提交和分支,构成了所有 Git 操作的底层结构。随着你学习本课程的进展,你将看到 git add、git commit、git branch、git checkout 和 git merge 等命令如何操作这些核心组成部分,从而有效地管理你项目的历史。