# 三、记录变更

维护项目的一系列“安全”修订是任何版本控制系统的核心功能。Git 通过记录项目的**快照**来实现这一点。录制快照后，您可以返回查看旧版本、恢复它们并进行实验，而不必担心破坏现有功能。

SVN 和 CVS 用户应该注意到，这与他们系统的实现有着根本的不同。这两个程序都为每个文件记录不同的内容——项目中变化的增量记录。相比之下，Git 的快照只是——*快照*。每个提交都包含它所包含的每个文件的完整版本。这使得 Git 非常快，因为文件的状态不需要每次被请求时都生成:

![](img/image007.png)

图 7:记录完整的快照，而不是版本之间的差异

本章介绍了使用工作目录、临时区域和提交历史记录创建快照的基本工作流程。这些是基于 Git 的修订控制的核心组件。

## 集结地

Git 的临时区域为您提供了一个在将提交添加到项目历史之前组织提交的地方。**分段**是将更改从工作目录移动到分段快照的过程。

![](img/image008.png)

图 8:参与提交的组件

它让你有机会从工作目录中挑选*相关的*变更，而不是一次提交所有的东西。这意味着您可以创建*逻辑的*快照，而不是*时间的*快照。这对开发人员来说是一个福音，因为它让他们将编码活动与版本控制活动分开。当你写特性的时候，你可以忘记停下来提交它们。然后，当您完成了您的编码会话，您可以通过这个阶段将变更分成任意多的提交。

要将新的或修改过的文件从工作目录添加到临时区域，请使用以下命令:

```
    git add <file>

```

要从项目中删除文件，您需要像添加新文件或修改后的文件一样将其添加到临时区域。下一个命令将执行删除并停止跟踪文件，但不会从工作目录中删除文件:

git rm --cached

## 检查舞台

查看存储库的状态是 Git 中最常见的操作之一。以下命令输出工作目录和临时区域的状态:

```
    git status

```

这将导致类似以下内容的消息(根据存储库的状态，某些部分可能会被省略):

```
    # On branch master
    # Changes to be committed:
    #
    #       new file:   foobar.txt
    #
    # Changes not staged for commit:
    #
    #       modified:   foo.txt
    #
    # Untracked files:
    #
    #       bar.txt

```

第一部分“要提交的更改”是您的暂存快照。如果你现在运行`git commit`，只有这些文件会被添加到项目历史中。下一节列出了不包括在下一次提交中的*跟踪的*文件。最后，“未跟踪的文件”包含工作目录中尚未添加到存储库中的文件。

### 生成差异

如果需要有关工作目录或临时区域中的更改的更多详细信息，可以使用以下命令生成一个 diff:

```
    git diff

```

这将输出工作目录中每个*未声明的*变化的差异。您也可以使用`--cached`标志生成所有*阶段*变更的差异:

```
    git diff --cached

```

注意项目历史不在`git status`范围内。要显示提交的快照，您需要`git log`。

![](img/image009.png)

图 9:`git status`范围内的组件

## 犯罪

提交代表项目的每个已保存版本，这使得它们成为基于 Git 的版本控制的原子单元。每个提交都包含项目的快照、用户信息、日期、提交消息以及整个内容的 **SHA-1 校验和**:

```
    commit b650e3bd831aba05fa62d6f6d064e7ca02b5ee1b
    Author: john <john@example.com>
    Date:   Wed Jan 11 00:45:10 2012 -0600

        Some commit message

```

这个校验和作为提交的唯一标识，这也意味着提交将永远不会被破坏或无意中被更改，而 Git 不会知道。

由于临时区域已经包含所需的变更集，提交不需要工作目录的任何参与。

![](img/image010.png)

图 10:提交快照所涉及的组件

要提交暂存快照并将其添加到当前分支的历史记录中，请执行以下操作:

```
    git commit

```

您将看到一个文本编辑器，并提示您输入“提交消息”提交消息应采用以下形式:

```
    <commit summary in 50 characters or less.>
    <blank line>
    <detailed description of changes in this commit.>

```

Git 使用第一行来格式化日志输出、通过电子邮件发送补丁等。，所以它应该简短，同时仍然描述整个变更集。如果你不能给出摘要行，这可能意味着你的提交包含了太多不相关的更改。您应该返回并将它们分成不同的提交。摘要后面应该有一个空行和对更改的详细描述(例如，您为什么进行更改，它对应于什么票号)。

## 检查提交

像存储库的状态一样，查看其历史是 Git 版本控制中最常见的任务之一。您可以通过以下方式显示当前分支的提交:

```
    git log

```

我们现在只有两个工具来检查 Git 存储库的每个组件。

![](img/image011.png)

图 11:`git status`与`git log`的输出

这也给了我们一个自然的命令分组:

*   阶段/工作目录:`git add`、`git rm`、`git status`
*   承诺历史:`git commit`、`git log`

### 有用的配置

Git 为`git log`提供了过多的格式化选项，这里包含了其中的一些。要在一行显示每个提交，请使用:

```
    git log --oneline

```

或者，要针对单个文件的历史记录而不是整个存储库，请使用:

```
    git log --oneline <file>

```

一旦您的历史超过一屏提交，过滤日志输出也非常有用。您可以使用以下内容来显示包含在`<until>`中但不包含在`<since>.`中的提交。两个参数都可以是提交标识、分支名称或标记:

```
    git log <since>..<until>

```

最后，您可以在每次提交中显示不同的变更。这对于查看特定提交影响了哪些文件非常有用。

```
    git log --stat

```

为了可视化历史，您可能还想看看`gitk`命令，它实际上是一个专门用于绘制分支的独立程序。详情运行`git help gitk`。

## 标记提交

标签是提交的简单指针，对于像公共发布这样的重要修订来说，它们非常有用。`git tag`命令可用于创建新标签:

```
    git tag -a v1.0 -m "Stable release"

```

`-a`选项告诉 Git 创建一个带*注释的*标签，让您记录一条消息(用`-m`指定)。

在没有参数的情况下运行相同的命令会列出您现有的标记:

```
    git tag

```