跳转至

3.提交你的修改

上一章告诉你如何克隆远程仓库到你的本地系统。在这一点上,你已经准备好开始对你的版本库进行修改了。这很好!

但是,很明显,仅仅对本地文件进行修改并不是你需要做的全部。你还需要对你的文件进行修改,让Git知道这些修改。一旦你完成了修改,你就需要告诉Git你想把这些修改提交到仓库。

什么是提交?

你现在可能已经猜到了,Git repo不仅仅是一个文件的集合;在它的表面之下,有很多东西在追踪你的改动的各种状态,更重要的是,要对这些改动做什么。

首先,回到你的分叉仓库的主页https://github.com/[your-username]/ideas,然后在仓库页面的顶部找到12 commits链接:

img

Note

如果你没有完成上一章的挑战,那么就去创建一个https://github.com/raywenderlich/ideas的分叉,并将其克隆到你的本地工作站。

点击这个链接,你会看到这个资源库的一些历史:

img

每个条目都是一个commit,本质上是版本库中的文件集在某个时间点的特定状态的快照。

一般来说,一个提交代表了对文件集的一些逻辑更新。想象一下,你正在为你的想法列表添加新的项目,而且你已经添加了很多你能想到的项目。你想把这部分工作以提交的方式记录到你的版本库中。

在你开始这些更新之前,版本库的状态--实际上是你的起点--是parent的提交。在你提交你的修改后,也就是diff,下一次提交就是child提交。下图对此有更多解释:

img

在这个例子中,我在两次提交之间给一个文件添加了新的文本。父提交是左边的文件,子提交是右边的文件。它们之间的差异是我对一个文件所做的修改:

img

而且,diff不仅仅是对文件的添加,创建新内容、修改内容和删除内容也是你对仓库中的文件所做的其他常见改动。

Git中,改变文件和创建提交之间有几个步骤。起初,这可能是一个有点沉重的方法,但是,当你通过建立你的提交,你会看到每个步骤是如何帮助创建一个工作流程,使你与你的仓库中的文件和它们发生了什么保持一致。

要理解建立提交的过程,最简单的方法是实际创建一个提交。你将创建一个文件的修改,看看Git是如何确认这个修改的,如何将这个修改分阶段,最后,如何将这个修改提交给仓库。

从改变开始

打开你的终端程序,导航到ideas仓库;在我的例子中,我把它放在GitApprentice目录中。这应该是你在前一章创建的分叉版本库的克隆。

Note

如果你错过了完成第二章末尾的挑战,现在回去按照挑战方案做,这样你就有一个本地克隆的分叉ideas库可以使用。

假设你想在books文件中添加更多的想法。在任何纯文本编辑器中打开books/book_ideas.md。我喜欢用nano,因为它快速而简单,而且我不需要记住任何晦涩的命令来使用它。

在文件的末尾添加一行,以捕捉一个新书的想法:"开发者的护理和喂养"。注意遵循与其他条目相同的格式。你的文件应该看起来像这样:

# Ideas for new book projects

- [ ] Hotubbing by tutorials
- [x] Advanced debugging and reverse engineering
- [ ] Animal husbandry by tutorials
- [ ] Beginning tree surgery
- [ ] CVS by tutorials
- [ ] Fortran for fun and profit
- [x] RxSwift by tutorials
- [ ] Mastering git
- [ ] Care and feeding of developers

完成后,保存你的工作,并返回到终端程序。

在后台,Git正在观察你所做的事情。不相信我?在ideas目录下执行以下命令,看看Git知道你做了什么,在这里:

git status

git status显示你的工作树的当前状态 - 也就是你正在工作的目录中的文件集合。在你的例子中,工作树是你的ideas目录下的所有文件。

你应该看到下面的输出:

~/GitApprentice/ideas $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   books/book_ideas.md

no changes added to commit (use "git add" and/or "git commit -a")

啊,这就是你刚刚修改的文件:books/book_ideas.mdGit知道你已经修改了它......但当GitChanges not staged for commit时是什么意思?

现在是时候转移一下注意力,看看你的文件在Git中的各种状态。建立Git各种状态的心理模型,对理解Git在做什么有很大帮助...特别是当Git做了一些你不太理解的事情时。

工作树和暂存区

working copyworking treeworking directory(语言很好,总是有不止一个名字)是你磁盘上的项目文件的集合,你可以直接使用和修改,就像你在上面的books/book_ideas.md中做的那样。

Git认为你的工作树中的文件处于三种不同的状态:

  • 未修改的
  • 修改过的
  • 阶段性的

Unmodified意味着你在上次提交后没有修改过这个文件。Modified则与此相反。Git认为你在上次提交后以某种方式修改了这个文件。但这个staged状态是什么?

如果你来自其他版本控制系统的背景,比如Subversion,你可能会认为commit只是把你所有的修改保存到仓库的当前状态。但Git是不同的,而且更优雅。相反,Git通过使用staging area的概念,让你在工作中逐步建立你的下一个提交。

Note

如果你曾经搬过家,你就会理解这个范式。当你为搬家打包时,你不会把你所有的东西都松散地扔到搬家车的后面(好吧,也许你会这么做,但你不应该,真的)。 相反,你拿一个纸板箱(中转区),把类似的东西装进去,摆弄一下,让所有东西都正确地装在箱子里,取出一些不完全属于自己的东西,再加上一些你忘记的东西。 当你对箱子的尺寸感到满意时,你就用包装胶带把箱子封起来,然后把箱子放到货车的后面。在这种情况下,你已经把箱子当作了你的中转区,把箱子贴起来,放在货车上,就像做了一个承诺。

基本上,当你在项目的各个部分工作时,你可以把一个或一组修改标记为staged,也就是告诉Git,"嘿,我想把这些修改放到我的下一次提交中...但我可能还有一些修改要给你,所以先把这些修改保留一下吧。"你可以在工作过程中从这个暂存区域添加和删除修改,只有在你准备好的时候才提交这组精心策划的修改到仓库中。

注意上面我说的是,"从暂存区添加和删除变化",而不是"从暂存区添加和删除文件"。这里有一个明显的区别,在你进行最初的几次修改时,你会看到这个区别。

暂存你的改动

Git非常有用,它(通常)会在命令的输出中告诉你该怎么做。回顾一下上面git status的输出,Changes not staged for commit部分给了你一些建议,让你知道该怎么做。

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)

所以,既然你想让这个改动最终提交到版本库,你会尝试第一个建议:git add

执行下面的命令:

git add books/book_ideas.md

然后,执行git status来查看你所做的结果:

~/GitApprentice/ideas $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   books/book_ideas.md

啊,这似乎好了一点。Git认识到你现在已经把这个改动放在了暂存区。

但你还需要对这个文件做另一个修改,而你却忘记了。既然你在读这本书,你也许应该在其中的Mastering git条目上打勾,标记为完成。

在你的文本编辑器中打开books/book_ideas.md,在方框中打上小写的x,标记该项目为完成:

- [x] Mastering git

保存你的改动并退出编辑器。现在,再次执行git status(是的,你会经常使用这个命令来确定你的方向),看看Git告诉你什么:

~/GitApprentice/ideas $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   books/book_ideas.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
  modified:   books/book_ideas.md

怎么了?Git现在告诉你,books/book_ideas.md既是*阶段性的,又是非阶段性的?这怎么可能呢?

记住,你在这里暂存的是变化,而不是文件。Git理解这一点,并告诉你有一个变化已经被暂存提交("关心和喂养开发者"的变化),还有一个变化尚未被暂存--将Mastering git标记为完成。

要看到这个细节,你可以告诉Git显示它所看到的变化。还记得我们之前说过的`diff'吗?是的,那是你的下一个新命令。

执行下面的命令:

git diff

你会看到与下面类似的东西:

diff --git a/books/book_ideas.md b/books/book_ideas.md
index 76dfa82..5086b1f 100644
--- a/books/book_ideas.md
+++ b/books/book_ideas.md
@@ -7,5 +7,5 @@
 - [ ] CVS by tutorials
 - [ ] Fortran for fun and profit
 - [x] RxSwift by tutorials
-- [ ] Mastering git
+- [x] Mastering git
 - [ ] Care and feeding of developers

这看起来很晦涩,但diff只是一种显示两个文件之间变化的简洁方式。在这个例子中,Git告诉你,你正在比较同一个文件的两个版本--你工作目录中的文件版本,以及你之前用git add命令告诉Git的文件版本:

--- a/books/book_ideas.md
+++ b/books/book_ideas.md

而且它还显示了这两个版本之间的变化:

-- [ ] Mastering git
+- [x] Mastering git

-前缀意味着删除了一行(或该行的一部分),+前缀意味着增加了一行(或该行的一部分)。在这个例子中,你删除了空格,增加了一个x字符。

你会在接下来的学习中了解到更多关于git diff的知识,但现在这些已经足够你使用了。是时候把你的最新改动分阶段了。

总是用git add键入你想分阶段的文件的全名是有点乏味的。而且,老实说,大多数时候你真的只想把你所做的所有改动分到阶段。Git为你提供了一个很好的快捷方式。

按键盘上的Q键退出差异视图,然后执行以下命令:

git add .

句号(或句号)告诉Git将所有的修改添加到暂存区域,包括这个目录和所有其他子目录。这很方便,你会在你的工作流程中经常用到它。

同样,执行git status可以看到你的暂存区有什么准备好了:

~/GitApprentice/ideas $ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  modified:   books/book_ideas.md

这看起来不错。没有任何未缓存的内容,你将看到对books/book_ideas.md的修改,这些修改已经准备好提交。

有趣的是,再执行一次git diff,看看有什么变化:

~/GitApprentice/ideas $ git diff
~/GitApprentice/ideas $

呃,这很有趣。git diff报告说没有变化。但如果你想一想,这是有道理的。git diff比较了你的工作树和暂存区。用git add .,你把工作树上的所有东西都放到暂存区,所以你的工作树和暂存区之间应该没有差异。

如果你想做得彻底(或者你还不相信Git),你可以要求Gitgit diff的末尾增加一个选项来显示它缓存提交的差异。

执行下面的命令,注意是两个--字符,而不是一个:

git diff --staged

你会看到一个类似于以下的差异:

diff --git a/books/book_ideas.md b/books/book_ideas.md
index 1a92ca4..5086b1f 100644
--- a/books/book_ideas.md
+++ b/books/book_ideas.md
@@ -7,4 +7,5 @@
 - [ ] CVS by tutorials
 - [ ] Fortran for fun and profit
 - [x] RxSwift by tutorials
-- [ ] Mastering git
+- [x] Mastering git
+- [ ] Care and feeding of developers

下面是已经改变的线路:

-- [ ] Mastering git
+- [x] Mastering git
+- [ ] Care and feeding of developers

你从Mastering git一行中删除了一些东西,在Mastering git一行中增加了一些东西,并增加了Care and feeding of developers一行。这似乎就是一切了。看来是时候把你的修改提交到仓库了。

提交你的改动

你已经做了所有的修改,你准备提交到版本库。只要执行下面的命令就可以进行第一次提交:

git commit

Git会带你进入一个相当混乱的状态。下面是我在终端程序中看到的情况:

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Your branch is up to date with 'origin/main'.
#
# Changes to be committed:
#       modified:   books/book_ideas.md
#

如果你以前没有被介绍过vim,欢迎你! VimGit在需要你输入自由文本时使用的默认文本编辑器。

如果你读了Git在那里提供的第一条小指令,就会发现Git在要求什么:

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.

啊--Git需要为你的提交提供一条信息。如果你回想一下本章前面看到的提交列表,你会发现每个条目都有一个小消息:

img

Vim中工作并不十分直观,但是一旦您知道了命令,就不难了。

按键盘上的I键进入Insert模式,您将看到屏幕底部的状态行更改为-- INSERT--以指示这一点。您可以在此随意键入内容,但请保持简单,并将消息限制在一行开始。

为提交消息键入以下内容:

Added new book entry and marked Git book complete

完成后,需要告诉Vim保存文件并退出。先按Escape键退出插入模式。

现在,键入冒号(Shift + ;在我的美式键盘上)进入Ex模式,该模式允许您执行命令。

要保存您的工作并一次性退出,请按顺序键入wq - 表示“write”“quit”,然后按Enter

:wq

你将被带回到命令行,并显示提交的结果:

~/GitApprentice/ideas $ git commit
[main 57f31b3] Added new book entry and marked Git book complete
 1 file changed, 2 insertions(+), 1 deletion(-)

就是这样! 这是你的第一次提交。更改了一个文件,插入了两个,删除了一个。这与本章前面的git diff中的内容相吻合。

现在您已经了解了如何提交对文件的更改,接下来将了解如何向存储库添加新文件和目录。

添加目录

您的项目中有目录来保存书籍、视频和文章的想法。但是如果有一个目录来存储编写教程的想法就好了。因此,您将创建一个目录和一个创意文件,并将它们添加到存储库中。

回到终端程序中,执行以下命令创建一个名为tutorials的新目录:

mkdir tutorials

然后,使用ls命令确认目录是否存在:

~/GitApprentice/ideas $ ls
LICENSE     articles    tutorials
README.md   books       videos

目录就在那里;现在你可以看到Git是如何识别新目录的。执行以下命令:

git status

您将看到以下内容:

~/GitApprentice/ideas $ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

呃,好像不太对。为什么Git看不到你的新目录? 这是Git的设计,它反映了Git看待文件和目录的方式。

Git如何查看工作树

在它的核心,Git实际上只知道files,而对目录一无所知。Git认为文件是“指向Git可以跟踪的实体的字符串”。如果你想一想,这是有道理的:如果一个文件可以被唯一地引用为该文件的完整路径,那么单独跟踪目录是非常多余的。

例如,下面是项目中当前所有文件的列表(不包括隐藏文件、隐藏目录和空目录):

ideas/LICENSE
ideas/README.md
ideas/articles/clickbait_ideas.md
ideas/articles/live_streaming_ideas.md
ideas/articles/ios_article_ideas.md
ideas/books/book_ideas.md
ideas/videos/content_ideas.md
ideas/videos/platform_ideas.md

这是Git如何查看项目的简化版本:存储库中跟踪的文件的路径列表。这样,Git在将仓库克隆到本地系统时,可以轻松快速地重新创建目录和文件结构。

在本书的中间部分,你会学到更多关于Git内部工作原理的知识,但现在,你只需要弄清楚如何让Git拾取一个你想要添加到仓库中的新目录。

.keep文件

Git识别目录的解决方案显然是在目录中放入一个文件,但如果你还没有任何东西要放进去,或者你想让每个人克隆这个项目时都看到一个空目录,该怎么办?

解决方案是使用占位符文件。通常的约定是在你想让Git“看到”的目录中创建一个隐藏的、零字节的.keep文件。

为此,首先使用以下命令导航到刚刚创建的tutorials目录:

cd tutorials

然后创建一个名为.keep的空文件,使用touch命令以方便使用:

touch .keep

Note

touch命令最初是用来设置和修改现有文件的“modified”“accessed”时间的。但是touch的一个很好的特性是,如果指定的文件不存在,touch会自动为你创建一个文件。 touch是打开文本编辑器创建和保存空文件的一个很好的替代方法。有经验的命令行用户大多数时候都使用此快捷方式。

执行以下命令查看此目录的内容,包括隐藏的点文件:

ls -a

您应看到以下内容:

~/GitApprentice/ideas/tutorials $ ls -a
.   ..  .keep

这是你的隐藏文件。让我们看看Git现在对这个目录的看法。执行以下命令以移回主项目目录:

cd ..

现在,执行git status来看看Git对当前情况的理解:

~/GitApprentice/ideas $ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)
    tutorials/

nothing added to commit but untracked files present (use "git add" to track)

Git现在知道那个目录里有东西,但是它是untracked,这意味着你还没有把那个目录里的东西添加到仓库里。使用git add命令可以很容易地添加该目录的内容。

执行下面的命令,它与git add的形式略有不同:

git add tutorials/\*

Note

上面两个斜杠的奇怪格式在bash shellzsh shell中应该是等效的,这是较新的macOS系统上的默认设置。

而你可以只用git add。和前面一样,添加所有文件,这种形式的git add是一种很好的方式,可以只添加特定目录或子目录中的文件。在本例中,您要告诉Git暂存tutorials目录下的所有文件。

Git现在告诉你它正在跟踪这个文件,并且它在暂存区:

~/GitApprentice/ideas $ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  new file:   tutorials/.keep

现在可以将此添加提交到存储库。但是,与使用Vim和文本编辑器调用整个业务不同,有一种快捷方式可以一次性将文件提交到存储库并添加消息。

执行以下命令将暂存的更改提交到存储库:

git commit -m "Adding empty tutorials directory"

您将看到以下内容,确认已提交更改:

~/GitApprentice/ideas $ git commit -m "Adding empty tutorials directory"
[main ce6971f] Adding empty tutorials directory
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 tutorials/.keep

Note

根据您所工作的项目或组织,您经常会发现在Git提交消息中包含哪些内容是有标准的。 本书的前几部分使用了一行提交消息,使事情变得简单,但是随着你在Git职业生涯中的发展,你会明白为什么遵循一些标准,比如Tim Popewww.example.com上提出的50/72规则https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.htmlhttps://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html,会让你在更深入地研究Git时变得更容易。

再次使用git status查看是否没有需要提交的内容:

~/GitApprentice/ideas $ git status
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

你可能已经意识到,所有这些小提交都能给予你看到Git对你的文件做了什么。而且,当你继续你的项目时,你可能想看到你所做的事情的历史视图。Git提供了一种查看文件历史记录的方式,也称为log

查看git日志

你在前几章做了很多事情。要查看您所做的操作,请执行以下命令:

git log

你会得到一堆输出下面是我的日志的前几部分:

commit 7f6c00a4e8aaac251f66244d7d685cefce899868 (HEAD -> main)
Author: Bhagat Singh <bhagatsingh2297@gmail.com>
Date:   Tue Oct 26 08:48:20 2021 +0530

    Adding empty tutorials directory

commit ba67184b4a934d56a1c0c3cc493ff5b3bf8cc062
Author: Bhagat Singh <bhagatsingh2297@gmail.com>
Date:   Tue Oct 26 08:38:31 2021 +0530

    Added new book entry and marked Git book complete

commit f65a79061758a3e52667d5bd6ba12efb4e7158c5 (origin/main, origin/HEAD)
Author: Bhagat Singh <bhagatsingh2297@gmail.com>
Date:   Thu Sep 9 12:17:55 2021 +0530

    Updated README.md to reflect current working book title.

commit c47084959448d2e0b6877832b6bd3ae70f70b187 (origin/master)
Author: Chris Belanger <chris@razeware.com>
Date:   Thu Jan 10 10:32:55 2019 -0400

    Going to try this livestreaming thing
:

.
.
.

你会看到你所有的提交,按时间倒序排列。

Note

根据您在终端程序中一次可以看到的行数,您的输出可能会被分页,使用类似less的读取器。如果您在终端屏幕的最后一行看到冒号,则可能是这种情况。只需按空格键即可阅读后面的文本。 当您到达文件末尾时,您将看到(END)。在任何时候,您都可以按Q键退出到命令提示符。

上面的输出显示了您自己的提交消息,在某种程度上,它们是有用的。因为Git知道你的文件的所有信息,你可以使用git log来查看提交的每一个细节,比如每次提交的实际更改或diff

要查看这一点,请执行以下命令:

git log -p

这会向您显示提交的实际差异,以帮助您查看具体更改了什么。以下是我的结果示例:

commit 7f6c00a4e8aaac251f66244d7d685cefce899868 (HEAD -> main)
Author: Bhagat Singh <bhagatsingh2297@gmail.com>
Date:   Tue Oct 26 08:48:20 2021 +0530

    Adding empty tutorials directory

diff --git a/tutorials/.keep b/tutorials/.keep
new file mode 100644
index 0000000..e69de29

commit ba67184b4a934d56a1c0c3cc493ff5b3bf8cc062
Author: Bhagat Singh <bhagatsingh2297@gmail.com>
Date:   Tue Oct 26 08:38:31 2021 +0530

    Added new book entry and marked Git book complete

diff --git a/books/book_ideas.md b/books/book_ideas.md
index 1a92ca4..5086b1f 100644
--- a/books/book_ideas.md
+++ b/books/book_ideas.md
@@ -7,4 +7,5 @@
 - [ ] CVS by tutorials
 - [ ] Fortran for fun and profit
 - [x] RxSwift by tutorials
-- [ ] Mastering git
+- [x] Mastering git
+- [ ] Care and feeding of developers
.
.
.

按照时间倒序,我将.keep文件添加到了tutorials目录中,并对book_ideas.md文件做了一些修改。

Note

第6章,“Git Log & History”,将深入研究git log的各个方面,并向您展示如何使用git log的各种选项来获得一些关于仓库活动的有趣信息。

既然您已经很好地理解了如何暂存变更并将其提交到存储库中,那么是时候迎接本章的挑战了!

挑战

挑战:添加一些教程想法

您已经有了一个很好的目录来存储教程的想法,所以现在是时候添加这些伟大的想法了。 您在本次挑战中的任务是:

  1. tutorials目录中创建一个名为tutorials_ideas. md的新文件。
  2. 向文件添加标题:# Tutorial Ideas
  3. 按照其他文件的格式,用一些想法填充该文件,例如[] Mastering PalmOS
  4. 保存您的更改。
  5. 将这些更改添加到临时区域。
  6. 使用适当的消息提交这些临时更改。

如果你遇到了困难,你可以在本章的projects/challenge文件夹中找到解决这个挑战的方法。

同样,如果您希望将您的存储库或目录的状态与我的进行比较,您还会发现我的ideas目录压缩在同一个文件夹中。

要点

  • commit本质上是存储库中的文件集在某个时间点的特定状态的快照。
  • working tree是您直接使用的项目文件的集合。
  • git status显示工作树的当前状态。
  • Git认为工作树中的文件处于三种不同的状态:未修改的、修改的和分段的。
  • git add<filename>允许您将工作树中的更改添加到临时区域。
  • git add .添加当前目录及其子目录中的所有更改。
  • git add <directoryname>/\*允许您添加指定目录中的所有更改。
  • git diff显示工作树和暂存区之间的差异。
  • git diff --staged显示了暂存区和上次提交到仓库之间的差异。
  • git commit提交暂存区中的所有更改,并打开Vim以便添加提交消息。
  • git commit -m "<your message here>"提交您的阶段性更改并包含一条消息,而无需通过Vim
  • git log显示仓库的基本提交历史。
  • git log -p显示仓库的提交历史以及相应的差异。

从这里去哪里?

现在你已经学会了如何在Git中创建提交,请继续阅读下一章,在那里你会学到更多关于暂存修改的技巧,包括Git如何理解文件的移动和删除,如何撤销你不想做的暂存修改,以及接下来的新命令:git resetgit mvgit rm