Git笔记(二)– 创建快照

​本文介绍如何使用Git为项目创建快照。我们将讨论Git经常被误解的基本概念,这是不少人使用Git时经常陷入困境的原因。


初始化存储库

创建目录Moon,我们假设这是我们的项目目录,进入该目录,用git init 初始化一个空存储库。

当初始化空存储库时,会在当前目录生成一个 .git 目录,默认它是隐藏的,该目录的内容就是Git的存储库。查看该目录,我们能看到hooks、info、objects、refs等目录,我们不必了解这些目录结构,而且随着Git版本的升级,这些目录也会改变的。

删除 .git 目录,就相当于删除存储库从而丢失了历史记录。


Git工作流程

当项目到达某个我们想保存的状态时,就提交到存储库。创建提交就像为项目拍摄快照一样。

Git有一个中间步骤被称为暂存区(Staging Area)或者叫索引(Index),其它的版本控制系统里一般没有。

暂存区是下一次提交的内容。我们更改后,将文件添加到暂存区,检查暂存区,如果没问题,就进行提交,这样快照将存储到存储库中。因此,暂存区允许我们在记录快照之前检查我们的工作。如果不想提交暂存区的某些文件,我们可以取消暂存,后续将它们作为另一个快照的一部分提交。这是基本的 git 工作流程。

看实际例子。

项目添加2个文件file1和file2,目前暂存区是空的,用 git add 命令将这2个文件添加到暂存区。

检查文件没问题的话,我们可以用 git commit 命令提交到存储库。

作为提交的一部分,我们提供一条有意义的消息(这里是”initial commit”)来说明本次快照,这对于历史记录的有用性至关重要。

现在存储库有了1 条提交(快照)。

Git的一个常见误解是,一旦提交,暂存区就会变空,这是不正确的,这也是很多人不理解Git的原因。目前暂存区与存储库最后的快照是相同的。暂存区与我们在将软件发布到生产环境时使用的暂存环境非常相似,它要么是目前在生产中拥有的,要么是即将投入生产的下一个版本的内容。

继续。

我们修改file1,现在暂存区的file1就是陈旧的了,用git add file1 将file1添加到暂存区,这样工作目录与暂存区的内容一致了。

同样的,检查暂存区没问题,用git commit 将暂存区的内容提交到存储库。

目前,存储库有了2条提交(快照)。

继续工作,我们删除了file2,但暂存区还有该文件,所以再一次用 git add file2 将file2添加到暂存区。

接着用git commit 提交到存储库。

目前存储库有了3条提交。

每一条提交包含一个由 Git 生成的唯一ID,它就像一个修订号。每条提交还包含有关由谁在何时更改了什么的信息,以及我们项目的完整快照。

与其他版本控制系统不同,Git不存储增量或更改的内容,而是存储完整内容。这样,它可以快速将项目恢复到早期快照,而无需计算。

这浪费空间吗?

不,Git在数据存储方面非常高效,它会压缩存储库,而且也不存储重复的内容。


暂存文件

使用 git add 命令暂存文件, 可以添加单个文件或多个文件,多个文件用空格分隔。也可以使用 *.txt这样的模式添加所有扩展名为txt的文件,我们也可以用一个点号 表示递归地添加整个目录的文件。

运行 git status,能看到暂存区有两个新文件file1.txt和file2.txt。

修改file1.txt,再运行git status查看。

提示工作目录的file1.txt未暂存,再次使用 git add 暂存。

git add file1.txt


提交更改

我们使用git commit 命令将暂存区进行提交。

git commit -m “Initial commit.”

提交时添加了备注信息“Initial commit”,这是一条简短的信息。如果要添加多行较长的信息该如何做呢?

直接输入,不带 -m 及后面的消息:

git commit

这样Git会打开编辑器允许我们编辑多行备注信息。第一行输入简短信息,空一格,再输入多行长消息。

保存回到提交完成界面,能看到提交的相关信息。


提交最佳实践

首先,所有的提交不能太大也不能太小

提交不能太小。不能文件一有更改时就进行提交,这样只会得到Update file1、Update file2、Update file3这样无意义的东西。

提交也不能太大。不必等到某项功能完全实现后再进行提交,不能编码几天后才进行一次提交。提交的目的是记录检查点,如果搞砸了,可以返回并恢复代码,所以要经常提交。实际上,每天会提交5到10次甚至更多,具体取决于您正在做的工作。因此,当达到想要记录的状态时,就进行提交。

其次,每个提交都应代表逻辑上独立的变更集

如果正在修复错误,却意外地在应用程序中发现了拼写错误,我们不应该在一次提交中提交这两个更改,而是应该分成两个单独的提交,一个提交用于修改拼写错误,另一个提交用于错误修复。

另外,提交的描述信息要有意义。

这也要求我们不能在同一个提交中做太多事情,否则提交描述信息做不到清晰简洁。现在不少团队更喜欢在他们的提交消息中使用现在时,比如使用fix the bug,而不是fixed the bug,所以应该根据团队的约定描述提交。


跳过暂存区

Git有个常见的问题:是否总要在提交之前暂存?

答案是否定的!

但只有当您 100% 确定您的代码不需要审查才可以这么做,因为这就是暂存区存在的意义。

使用 git commit 命令时添加 -a 参数可以跳过暂存区。

修改file1.txt文件,然后进行提交。

-a 参数表示all,意思是全部未修改的文件,两个参数 -a 和 -m 也可以合并。

git commit -am “XXX”

再次说明,只有非常确认不用审查代码才这么做。绝大多数情况下都是先添加到暂存区,再进行提交。


删除文件

删除文件file2.txt,再用 git status 查看,发现工作目录与暂存区不一致,file2.txt还存在于暂存区,用以下命令检查暂存区:

git ls-files

目前暂存区有2个文件,file2.txt确实还存在。

我们将file2.txt删除的变动信息添加到暂存区:

git add file2.txt

再使用 git ls-files 查看,发现file2.txt已不存在了。这样工作目录与暂存区是一致的了。

所以,删除文件时,我们必须同时在工作目录和暂存区删除它。对于这么常见的操作,Git提供了合二为一的操作命令:git rm。

git rm file2.txt

git rm 可以删除多个文件,用空格分隔,如“git rm file1.txt file2.txt”;也可以用模式删除,如“git rm *.txt”表示删除所有扩展名为txt的文件。


重命名或移动文件

重命名file1.txt为main.js,再用git status检查,发现有两个变动未暂存。一是删除file1.txt,二是新建文件main.js。我们要把这两个变动暂存:

git add file1.txt

git add main.js

再用git status检查,发现file1.txt重命名为main.js的变动已暂存,处在待提交的状态。

所以重命令文件也是两步操作,首先在工作目录重命令文件,然后再将删除和新建两个变动添加到暂存区。

类似地,Git也提供了一个合二为一的命令用于重命名操作:git mv。

git mv main.js file1.js


忽略文件

有些文件我们是不想保存到存储库的,比如日志。

创建logs目录,再在该目录下创建dev.log文件,用 git status 查看状态,提示工作目录与暂存区不一致。

但我们不想存储这些文件, 所以不将logs目录里文件添加到暂存区。

添加一个特殊的文件 .gitignore,它没有名字只有扩展名,并且放在项目文件夹的根目录。.gitignore文件的每一行指定了不想在存储库跟踪的文件或目录。

打开 .gitignore文件,添加要忽略的内容,比如logs/ 表示logs目录,以及main.log、*.log等,*.log表示所有扩展名为log的文件。

保存回到控制台,输入 git status 查看,发现只有 .gitignore,而 logs已被Git忽略了。

但要知道,如果已经将文件或目录提交到了存储库,再在 .gitignore里添加这些文件和目录将是无效的。

添加目录bin,在bin目录里添加app.bin文件,再提交存储库。

现在再在 .gitignore里添加bin目录表示要忽略该文件夹。

保存并提交后,我们再来修改bin目录的app.bin,查看一下Git状态,发现bin并未被忽略。

要想解决这个问题,得在暂存区删除掉bin目录。用git ls-files检查暂存区,发现bin目录是在的。

之前我们介绍过 git rm 命令可以同时删除工作目录和暂存区的文件,但我们只想从暂存区删除而工作目录的文件需要保留,加上参数 –cached 就可以仅删除暂存区。

git rm –cached -r bin/

这里的 -r 表示递归删除bin目录里的全部内容。

用 git ls-files 检查 bin目录已删除。

用git status 检查发现下一次提交将删除存储库的 bin目录文件。

提交。现在再来修改 bin/app.bin,再用 git status 检查,发现 bin 目录已被Git忽略了。

访问 https://github.com/github/gitignore,可以看到很多 .gitignore的模板,可以根据实际项目类型选择使用。


简洁状态

我们已知道,git status用于查看工作目录和暂存区的状态,显示的内容比较详细,但太长了,可以加上 -s 使其简单一点。

git status -s

看个例子。修改file1.js,再添加file2.js文件。 再用 git status -s 查看。

显示非常简洁,文件前的状态有两列,第一列代表暂存区的状态,第二列代表工作目录的状态。我们修改了file1.js但尚未暂存,所以工作目录的状态是红色的M(修改),第一列的暂存区状态为空。而file2.js在两个均是问号。

将file1.js暂存,查看状态,暂存区已变成绿色的M,而工作目录则为空了。

再对file1.js进行修改,查看状态。工作目录和暂存区均是M。

将file1.js暂存,file1.js在工作目录状态将为空,暂存区则为绿色的M。

现在来看file2.js,我们将其暂存并查看状态。

file2.js在暂存区的状态变成了A,这是添加(Add)的意思。工作目录的file2.js由于已暂存,所以状态是空。


查看暂存和未暂存的更改

作为最佳实践,在将暂存区提交存储库前要进行检查,git status命令能显示文件的状态,但如果想查看文件差异的话,就需要使用命令:

git diff –staged

该命令用于查看暂存区的哪些变动准备提交到存储库。坦率讲,使用控制台窗口查看暂存区的文件差异不是好方法,后面我们会介绍图形工具,但了解该命令是必要的,因为某些时候是没有图形界面的,我们要看得懂。

它比较新旧两个版本,a 表示老版,也就是已在存储库内容,b表示在暂存区的新版本。老版本用 – 表示,新版本前面则是 + 。如果文件内容较多就分段显示,上图file1.js比较的就是老版本第4行开始的3行 和 新版本每4行开始的5行进行比较,而file2.js由于新添加且只有1行,就显示了全部内容。

如果要比较工作目录与暂存目录,则不用加 –staged:

git diff

目前我们的工作目录与暂存目录是一致的,可以用git status -s 验证。修改file1.js使其不一致,再查看他们的区别。

a 表示是暂存区的老版本,b表示工作目录的新版本。

总之,git diff 不加参数比较暂存区与工作目录,git diff –staged 则比较存储库与暂存区。


可视化工具

利用git diff 在控制台查看差异不太直观,可以使用可视化工具,有如下一些工具可用。

这里使用vscode。

首先得告诉Git我们使用vscode作为默认的diff工具。

git config –global diff.tool vscode

git config –global difftool.vscode.cmd “code –wait –diff $LOCAL $REMOTE”

$LOCAL、$REMOTE表示占位符,表示文件的老版本和新版本。

配置之后,就可以像使用git diff一样使用git difftool了。

git difftool –staged 用于比较存储区与暂存区。

git difftool 用于比较暂存区与工作目录。

现在就可以非常直观地查看差异了。

但老实说,这种差异图形工具一般不再单独使用,因为几乎所有的IDE都已集成了。


观看历史记录

查看快照历史记录的命令是:

git log

图示master是主分支,Git可以有多个分支,各分支可用于实现不同的功能,然后进行合并。head指向当前的分支。其它还有作者的姓名、电子邮件、提交的时间、描述说明等信息。

git log 常用的参数有 –oneline,它用于单行显示每个提交;–reverse 则用于反转显示顺序。


查看具体的快照

我们已了解如何查看提交的列表,如果要查看某个具体的提交该如何做?

使用 git show 命令。

git show 可以使用提交的ID,其中ID不一定要写全,只要前面几位且不会与其他混淆即可。也可以使用HEAD,HEAD表示当前的指针,HEAD加波浪符号和数字表示当前提交的向前的第几个,HEAD~1表示指针前一个提交,HEAD~2则是表示指针向前第2个提交,依次类推。

如果我们想只想查看某个提交的最终版本,而不是差异,可以加冒号和全路径文件名。比如我们想查看某个提交的.gitignore的内容,可以使用:

git show <提交的ID>:.gitignore

当然也可以用 HEAD。

git show HEAD~1:bin/app.bin

注意路径要写全,如图的 bin/app.bin。

如果要列出某个快照(提交)的所有文件,可以使用 git ls-tree,如

git ls-tree HEAD~1

Git列出了文件和目录,文件类型是blob,而目录则是tree,根据列出的ID可以查看具体的内容,也用 git show 命令。我们看到,对于文件显示其内鸩,而对于目录则列出其下的文件。

总之,使用 git show 命令,我们可查看Git数据库的对象,这些对象可能是提交(commits),可能是blob,可能是tree,也可能是Tag。Tag的概念我们会在以后介绍。


取消暂存文件

假设我们不想file1.js下一次提交到存储库,就要取消暂存该文件。以前使用的命令是git reset,但这个命令容易引起混乱,现在一般使用git restore。

git restore –staged file1.js

git restore命令也可以取消暂存多个文件(空格分隔),或用模式(如 *.js),或者全部(用点 . 表示),通过在执行该命令前后执行 git status -s,可以看出变化。

git restore的工作原理是,它用下一个环境的最新版本覆盖。对于暂存区而言,下一个环境就是存储库的最后一个快照(提交)。

再来试一下file2.js,它尚未提交到存储库,所以执行git restore file2.js后,暂存区的file2.js就是空,用git status -s 查看,就是两个 ?,因为恢复后file2.js尚未被Git跟踪。


放弃本地更改

假设我们不喜欢对file1.js所做的修改,想放弃更改,该如何做?

也是使用 git restore ,不加 –staged 参数。

git restore file1.js

也可以放弃所有的更改,使用 . 符号。

git restore .

我们发现,即便使用了 git restore . 也就丢弃所有修改,但file2.js还是存在的,为什么?

原因很简单,file2.js是新增文件,Git还未对他进行跟踪,自然没法处理。

如果我们想要删除所有未跟踪的文件,可以使用 git clean。由于默认保护机制,需要加上 -fd 参数,f 表示强制, d 表示删除文件夹。

git clean -fd


将文件恢复到早期版本

假设我们误删了file1.js且已提交到存储库,如何恢复?

默认情况下,git restore 从下一个环境恢复文件,但我们也可以修改这个默认行为。使用 git restore -h 查看帮助,可以看到有一个 –source 的参数,我们就利用它将file1.js从最后提交的前一个提交(HEAD~1)进行恢复。

git restore –source=HEAD~1 file1.js


使用 VSCode 创建快照

我们利用VSCode来查看操作Git。能将 git status 和 git diff的相关输出进行图形化显示。

我们点击file1.js的 + 号将其添加到暂存区,然后进行提交。

完成提交后,点击file1.js,点开TIMELINE栏,可以看到file1.js各个提交的变化,非常方便比较。


使用 GitKraken 创建快照

用GitKraken打开Moon存储库,可以查看各提交,点击某个提交查看详细信息。

点击某个文件,可以查看文件的差异。利用inline视图,可以查看类似命令行git diff 输出的内容。

我们修改file1.js,GitKraken提示有未暂的文件,可以进行暂存,暂存后也可进行取消暂存。然后提交。


小结

本文介绍了使用Git创建快照的知识,包括Git的工作流程,项目的暂存、提交等概念和操作,除了命令行的操作,也介绍了图形界面的操作。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注