Git 秘籍(一)
原文:Git Recipes
一、Git 入门
计算机软件制造商正面临着由微不足道的原因引起的困难挑战。一个典型的应用是由一个开发团队在很短的时间内处理成百上千个文件而产生的。每个文件都需要随时可供所有开发人员修改。当我们用时间线来补充这个场景时,情况就更加复杂了。任何开发人员都可以在任何时候修改每个文件。以下三个简单的因素使得源代码管理成为一项重要的任务:
- 文件的数量
- 开发者的数量
- 时间线
这些问题很多年前就已经知道了,正如你所预料的,有各种各样的软件工具可以使团队处理文本文件变得更加容易。这些工具通常被称为版本控制软件或版本控制软件。而 git 就属于这个家族。
git 是什么?
Git 是一个分布式版本控制系统,旨在支持 Linux 内核的开发。它由 Linus Torvalds 于 2005 年 4 月创建,现在由 Junio C. Hamano 维护。
git 区别于其他版本控制系统的主要特性是:
- 分支
- 数据完整性
- 位置
- 分布式的计算机系统
- 开放源码
- 最后但同样重要的是——受欢迎程度
分支模型是 git 最惊人的特性。我认为这本身就是切换到 git 的充分理由。使用 git,几乎可以瞬间创建分支,并且可以很容易地与其他开发人员合并和共享。虽然有许多复杂的操作可以在分支上执行,但是基本的用法是简单明了的。这鼓励了分支的广泛使用,我认为我说 git 分支模型改变了开发人员的工作方式并不夸张。
数据完整性 意味着 git 跟踪项目的所有文件和目录,这样就不可能引入未被注意到的更改。即使你想改变一个字节,你也必须创建一个版本。当你创建一个版本时,没有办法隐藏里面的东西。这是一个无法关闭的内置功能。因此,您可以完全信任 git:所有的更改都是作为修订版引入的,并且每个修订版都可以被检查。
Locality 提高了 git 的效率,并允许您即使在网络中断的情况下也能执行许多 git 命令。当您使用 git 时,您没有连接到任何服务器。大多数命令,比如 commit、branch、merge 和 rebase,都是以类似于 mkdir、ls、rm 等典型文件系统命令的方式在本地执行的。他们不进行任何数据传输。
因为 git 是一个分布式版本控制系统,所以每个存储库都是完全功能性的,既可以作为发送者也可以作为接收者。如果计算机之间有通信通道,它们的存储库可以双向交换内容。因此,您可以创建比集中式修订控制系统使用的客户机/服务器模式更复杂的工作流。
此外,git 是一个开源项目的事实,它正在成为世界上最流行的版本控制系统——你会发现有很好的理由开始学习 git。
1-1.在 Windows 上安装 git
问题
你想在 Windows 上安装 git。
解决办法
转到http://msysgit.github.io/并下载最新版本的 git for Windows 安装程序。在撰写本文时,这是版本 1.8.3。安装者名叫:Git-1.8.3-preview20130601.exe。运行安装程序,将所有选项设置为默认值。之后,git 就可以在您的系统上运行了。
它是如何工作的
在 Windows 上安装 git 有两种方法:
- 使用位于
http://www.cygwin.com的 Cygwin 包 - 使用名为 msysgit 的独立安装程序。
在这个过程中,我们使用 msysgit 包。
当您运行从http://code.google.com/p/msysgit/下载的 msysgit 安装程序时,您会被问到两个问题:
- 如何配置路径?
- 如何配置行尾字符的转换?
标题为调整路径环境的对话框设置路径环境变量。Msysgit 安装程序不仅包含 git 二进制文件,还包含许多 Linux 程序,如ssh、curl、ls、mkdir、rm和find。使用默认设置,安装程序将 git 和这些程序复制到以下目录:
C:\Program Files (x86)\Git\bin
该文件夹包含ls, mkdir, ssh, curl, find等。
C:\Program Files (x86)\Git\cmd
该文件夹包含 git 二进制文件和运行 git 的 shell 脚本
对话框中的第一个选项是仅使用 Git Bash】。使用此设置,path 变量不会被修改。当您启动 Windows 命令行并键入 git 时,命令行将响应一条消息,指出 git 命令不存在。
第二个选择是从 Windows 命令提示符运行 Git,将 C:\Program Files (x86)\Git\cmd 文件夹添加到您的路径中。因此,您可以在 windows 命令行中使用 git 命令。但是,当您在 windows 命令行中键入 ssh 时,系统将会响应一条未知的命令消息。
最后一个选择是从 Windows 命令提示符运行 Git 和包含的 Unix 工具。该选项将两个文件夹 C:\Program Files (x86)\Git\bin 和 C:\Program Files (x86)\Git\cmd 添加到您的路径中。现在,您可以在 Windows 命令行中使用所有包含的工具:ssh、git、ls、curl 等等。但是 C:\Program Files (x86)\Git\bin 中的一些命令,如 find,与 Windows 中原来可用的命令重叠。原来的查找现在在命令行中不可用。
当我写这本书的时候,我的意图是展示可以在所有平台上以完全相同的方式工作的命令。因此,我决定使用 bash 命令行。如果您在 Windows 上工作并且想要使用 bash 命令行,那么您可以保留默认的首选只使用 Git Bash】。
第二个对话框名为配置行尾转换,将名为core.autocrlf的配置选项设置为以下值之一:true、input或false。该设置的含义总结在表 1-1 中。
表 1-1 。core.autocrlf 选项的所有值及其对签出和提交操作的影响
|价值
|
检验
|
犯罪
|
| --- | --- | --- |
| True | LF => CRLF | CRLF => LF |
| input | 没有人 | CRLF => LF |
| false | 没有人 | 没有人 |
当您选择第一个设置值true时,git 将在签出和提交操作期间转换文件中的行尾字符。当您签出文件时,git 会将 LF 转换为 CRLF,当您提交时,git 会将 CRLF 转换为 LF。
第二个选择是input,只有在提交时才转换新行。在本例中,git 将行尾从 CRLF 转换为 LF。
第三个设置(false)关闭所有转换。
行尾字符的转换在配方 13-2 到 13-6 中有更详细的解释。无论您当前选择哪一个,您都可以使用以下命令之一来更改设置:
$ git config --global core.autocrlf true
$ git config --global core.autocrlf input
$ git config --global core.autocrlf false
安装完成后,运行开始菜单中的 git bash 应用。要验证安装是否成功,您可以运行以下命令:
$ git --version
它应该打印出您的系统上安装的 git 版本。
提示如果您想将当前目录更改为 c 盘的根目录,请使用以下命令:
$ cd /c
这相当于命令:c:
1-2.在 Linux 上安装 git
问题
你想在 Linux 上安装 git。
解决办法
根据您的系统,运行以下命令之一:
# for Ubuntu
$ sudo apt-get install git
# for Fedora
$ sudo yum install git
它是如何工作的
在 Linux 上安装 git 最简单的方法是使用可用的包。如果你想使用 git 的源代码来编译和安装 git,请遵循方法 11-3 中描述的步骤。
要验证安装是否成功,您可以运行以下命令:
$ git --version
它应该打印出您的系统上安装的 git 版本。
1-3.在 OS X 上安装 git
问题
你想在 OS X 上安装 git。
解决办法
访问http://code.google.com/p/git-osx-installer/站点并下载 git 的最新可用版本。运行下载的安装程序,将所有选项设置为默认值。
它是如何工作的
在 OS X 上安装 git 最简单的方法是使用图形化安装程序。要验证安装是否成功,您可以运行以下命令:
$ git --version
它应该打印出您的系统上安装的 git 版本。
1-4.访问手册
问题
你想访问 git 手册。
解决办法
运行以下命令:
$ git help
$ git help -a
$ git help -g
$ git help commit
$ git commit --help
$ git help merge
$ git merge --help
它是如何工作的
Git 命令分为两大类:
- 瓷器命令
- 管道命令
命令是日常使用的高级命令。除其他外,这一群体包括:
$ git add
$ git commit
$ git help
$ git push
另一组称为管道 ,包含低级命令。以下是一些例子:
$ git receive-pack
$ git update-index
$ git upload-pack
默认情况下,命令$ git help只列出了瓷瓶命令。如果你想列出管道命令和瓷器命令,使用-a开关$ git help -a。
您可以使用以下语法访问特定 git 子命令的文档:
$ git help [COMMAND]
$ git [COMMAND] --help
以下是访问$ git commit命令文档的命令:
$ git help commit
$ git commit --help
1-5.配置 git
问题
您希望配置 git 以准备工作。
解决办法
运行以下命令:
$ git config --global user.name
它应该打印出空的结果。这是因为在安装之后,用户名没有被配置。使用以下命令设置 user.name 配置选项:
$ git config --global user.name "John Doe"
输入你的姓名,而不是无名氏。
接下来,运行命令:
$ git config --global user.email john.doe@example.net
此命令将设置您的电子邮件。
它是如何工作的
如果您想在 git 存储库中创建提交,您必须配置两个设置:user.name 和 user.email。用作 user.name 和 user.email 值的字符串将存储在您创建的每个提交中。
二、使用著名的存储库
我们将开始我们的旅程,探索现有的和相当著名的仓库。本章的主要目标是熟悉存储库——它们的类型和结构。在本章中,您将学习以下内容:
- git 存储库最流行的托管解决方案是什么?
- 如何创建托管在
Github.com或Bitbucket.org上的存储库的本地副本?
一旦我们知道如何克隆一个存储库,我们就可以分析它的结构。然后我们将探索工作目录 git 目录及其内容。在这一点上,我们将能够将一个存储库分类为裸露的或者非裸露的。
接下来,我们将讨论打印存储库信息的各种命令,例如
- 修订列表
- 贡献者列表
- 修订的数量
- 贡献者的数量
- git 目录和工作目录的磁盘使用情况
为了使输入长 git 命令变得更容易,我将定义它们的别名。
注我已经在 Linux 和 Windows 两个平台上测试了本章和书中介绍的所有命令。我的目的是提供一套不管你的平台如何都能工作的指令。为了实现这个目标,文件系统操作是用 Linux 命令执行的,比如
ls和rm。此外,清单以$开头,路径使用/作为分隔符——表明它们是为 Linux 准备的。但是,如果您使用的是 Windows,请不要担心。只需使用 git 发布的 bash 命令解释器,所有的命令都会运行良好。
如果您使用的是不同于 Linux 的类 Unix 系统,一些命令(即du或echo)可以使用与本书中介绍的不同的开关。因此,您需要自定义这些命令。
2-1.克隆托管在 Github 上的存储库
问题
您想要获得 jQuery 存储库的本地副本。
解决办法
启动命令行并创建git-recipes/和git-recipes/02-01/目录:
$ cd /some/where/on/your/system
$ mkdir git-recipes
$ cd git-recipes
$ mkdir 02-01
将当前目录更改为02-01/:
$ cd 02-01
然后执行如清单 2-1 所示的命令。
清单 2-1。 命令克隆 jQuery 库
$ git clone https://github.com/jquery/jquery.git
注意在克隆命令期间,git 将一个完整的存储库从其原始位置复制到您的本地存储系统。根据您的带宽和项目大小,此操作可能需要相当长的时间。但是不用担心。克隆只在您设置本地存储库时进行一次。所有后续的数据传输都非常高效,因为 git 只传输丢失的数据部分。克隆的内部原理在第十章(配方 10-1)中有解释。
在您运行清单 2-1 中的命令后,您的git-recipes/02-01/目录将包含如图 2-1 中所示的文件和目录。注意,jQuery 存储在子目录jquery/下,而不仅仅是在git-recipes/02-01/中。
图 2-1 。git 克隆命令后 git-recipes/02-01/目录的内容
注意事项 图 2-12013 年 4 月编制。正如您所猜测的,jQuery 项目一直在向前发展。因此您的
git-recipes/02-01/目录的内容可能会有所不同。
它是如何工作的
要克隆 jQuery,您必须找到指向其存储库的 URL。启动网络浏览器,转到Google.com,搜索"jquery git repository"。结果将包括:
https://github.com/jquery/jquery
以类似的方式,您可以找到其他流行的开源项目的 URL。表 2-1 列出了另外三个项目的关键词和 URL:Linux、git 和 Mozilla。
表 2-1 。如何找到其他项目的 git 仓库?
|
在Google.com中搜索的短语
|
存储库的 URL
|
| --- | --- |
| linux git 仓库 | https://github.com/torvalds/linux |
| git git repository | https://github.com/git/git |
| mozilla git 存储库 | https://github.com/mozilla/mozilla-central |
一旦知道了 jQuery 存储库的 URL,就可以启动 web 浏览器并访问:
https://github.com/jquery/jquery
您将看到如图图 2-2 所示的页面。
图 2-2 。jQuery 知识库的主页面https://github.com/jquery/jquery
Github 上存储的每个库都可以在两个不同的 URL 下使用:HTTPS 和 SSH。您可以使用图 2-2 中箭头所指的按钮复制它们。jQuery 储存库的 URL 是:
HTTPS: https://github.com/jquery/jquery.git
SSH: git@github.com:jquery/jquery.git
如果您没有安装了 SSH 密钥的 Github 帐户,则不能使用 SSH URL。你必须选择 HTTPS 网址。
提示在第十三章我们将创建并配置一个 Github 账户。然后你也可以使用 SSH URL 在那之前,你必须使用 HTTPS 网址。
这样,您可以克隆在Github.com上可用的所有存储库。记住这个命令:
$ git clone https://github.com/abc/def.git
因为它创建了子目录def/。克隆体就储存在里面。
但是,如果您在 Windows 上工作,并且试图克隆 Linux 源代码,例如,您将会遇到问题,因为不同的系统有不同的文件名限制。我们会在第十一章对此进行分析。
如果没有配置了 SSH 密钥的 Github 账号,使用 SSH URL 会怎么样?jQuery 的 SSH URL 是:
git@github.com:jquery/jquery.git
如果您将它用于 git clone 命令:
$ git clone git@github.com:jquery/jquery.git
那么您将得到以下错误:
Permission denied (publickey)
2-2.克隆 Bitbucket 上托管的存储库
问题
您想要获得存储在Bitbucket.org上的亚特兰蒂斯 AUI 库的本地副本:
https://bitbucket.org/atlassian/aui
您希望克隆文件直接保存在git-recipes/02-02/下,而没有额外的aui/子目录。
解决办法
启动命令行并创建一个git-recipes/02-02/目录:
$ cd git-recipes
$ mkdir 02-02
将您当前的目录更改为02-02/:
$ cd 02-02
然后运行清单 2-2 中所示的命令。注意最后一个参数——一个点。这个点代表当前目录,因此克隆将直接放在git-recipes/02-02/下。如果没有点,克隆的存储库将被存储在子目录git-recipes/02-02/aui/中。
清单 2-2。 命令克隆 AUI 库
$ git clone https://bitbucket.org/atlassian/aui.git.
注意git 克隆命令的语法是:
$ git clone URL [directory]。如果使用的话,可选的[directory]参数为克隆的存储库设置目标目录。
它是如何工作的
启动您的网络浏览器并转到https://bitbucket.org/atlassian/aui。亚特兰大 AUI 知识库的主页如图 2-3 所示。
图 2-3 。亚特兰蒂斯 AUI 知识库主页https://bitbucket.org/atlassian/aui
Bitbucket 为每个存储库提供了两个 URL:HTTPS 和 SSH。图 2-3 和下面的列表将为您提供对它们的访问:
HTTPS: https://bitbucket.org/atlassian/aui.git
SSH: git@bitbucket.org:atlassian/aui.git
与 Github 一样,SSH URL 只能在您拥有一个配置了 SSH 密钥的 Bitbucket 帐户时使用。
提示git 最流行的两个托管解决方案是
Github.com和Bitbucket.org。两者都为公共存储库提供无限制的免费账户。
2-3.克隆本地存储库
问题
你想要克隆一个你在配方 2-1 中创建的库git-recipes/02-01/jquery/,并且你更喜欢在git-recipes/02-03/下直接存储一个新的克隆,而不需要一个额外的jquery/目录。
解决办法
转到您的git-recipes/目录:
$ cd git-recipes
然后你需要执行清单 2-3 中的命令。
清单 2-3。 克隆本地存储库的命令
$ git clone 02-01/jquery 02-03
使用清单 2-3 中所示的命令后,目录git-recipes/02-03/中将包含如图 2-4 中所示的文件。
图 2-4 。成功克隆后 git-recipes/02-03/目录的内容
它是如何工作的
Git clone 命令接受指向存储库的 URL 和本地路径。因此,您可以通过向命令传递路径而不是 URL 来克隆本地存储库。
2-4.复制本地存储库
问题
您想要克隆您在配方 2-3 中创建的存储库git-recipes/02-03/。这一次,你想要使用一个带有–R 标志的标准cp命令,而不是使用git clone。
解决办法
进入目录git-recipes/:
$ cd git-recipes
第二,执行以下命令:
$ cp -R git-recipes/02-03 git-recipes/02-04
该命令将从git-recipes/02-03/创建文件的精确副本。git-recipes/02-04/的内容是一个有效的 git 存储库。
它是如何工作的
通过使用cp -R命令,您可以递归地复制一个目录。如果在包含存储库的目录上使用,它将创建一个正确的存储库,该存储库与使用git clone命令创建的存储库几乎相同。我们将在方法 2-5 中探索在方法 2-3 和 2-4 中创建的存储库之间的区别。
提示一旦您知道可以使用标准的文件系统操作(如
cp)来复制一个存储库,您可以使用rsync或scp来达到同样的效果。在第十一章中,我们将使用 scp 命令启动一个新项目。
2-5.探索 git 存储库的内容
问题
git clone命令创建的目录包含什么?要回答这个问题,您需要用cd、ls和cat命令探索git-recipes/02-03/目录的内容。你也可以使用你最喜欢的文件管理器。
解决办法
git-recipes/02-03/目录的内容如图图 2-5 所示,包含 jQuery 文件和目录以及一个名为.git的特殊目录。
图 2-5 。在配方 2-3 中创建的存储库
名为.git的目录称为 git 目录 。它包含关于存储库的所有信息。.git目录的内容如图 2-6 所示。
图 2-6 。. git 目录的内容
它是如何工作的
如果你想显示一个.git目录的内容,你可以使用以下命令:
$ cd git-recipes/02-03/
$ cd .git
$ ls -l
最后一个命令将打印图 2-6 中所示的文件和目录。在表 2-2 中简要描述了每个项目的作用。完整的描述包含在处理 git 具体细节的方法中。
表 2-2 。. git 目录的内容
|
目录/文件
|
描述
|
| --- | --- |
| hooks/ | 目录包含当某些事件发生时 git 可以自动执行的脚本;例如,在每次提交之前和之后。 |
| info/ | 目录包含一个名为exclude的文件,可以用来从存储库中排除文件。与.gitignore文件不同,该文件不被其他人共享。 |
| logs/ | 目录包含对存储库所做的本地更改的日志。 |
| objects/ | 这是包含文件、目录、修订和标签的所有信息的数据库。 |
| refs/ | git 在这里存储关于分支和轻量级标签的信息。 |
| config | 这是本地配置文件,包含将仅应用于此存储库的选项。 |
| description | 这是存储库的简短描述。它由 git 发布的 Gitweb CGI 应用使用。 |
| HEAD | 存储库的当前分支或版本 |
| index | 存储库的临时区域 |
| packed-refs | 打包格式的来自refs/的参考文献列表 |
现在让我们比较三个配置文件的内容:
git-recipes/02-01/jquery/.git/config
git-recipes/02-03/.git/config
git-recipes/02-04/.git/config
配方 2-1 中创建的第一个解决方案是存储在 Github 上的原始 jQuery 存储库的克隆。在git-recipes/02-01/jquery/.git/config中,你会发现以下几行:
[remote "origin"]
url = https://github.com/jquery/jquery.git
条目[remote "origin"]存储传递给git clone命令的地址。
第二个解决方案是本地目录的克隆。文件git-recipes/02-03/.git/config包含:
[remote "origin"]
url = /home/john/git-recipes/02-01/jquery
如你所见,这次[remote "origin"]指向本地目录。
提示我假设你的
git-recipes/目录的完整路径是:/home/john/git-recipes/。
第三种解决方案是对git-recipes/02-03/的精确复制。因此,文件git-recipes/02-04/.git/config包含:
[remote "origin"]
url = /home/john/git-recipes/02-01/jquery
如果我们使用:
$ cd git-recipes
$ git clone 02-03 02-04
创建git-recipes/02-04/;文件git-recipes/02-04/.git/config应该包含:
[remote "origin"]
url = /home/john/git-recipes/02-03
git-recipes/02-03/和git-recipes/02-04/没有任何区别。稍后,在第十章中,我们将学习使用git remote命令改变配置文件中的[remote "origin"]条目。
结论
作为方法 2-4 和 2-5 的结论,记住存储库可以被复制和移动到你的驱动器上的不同位置;就像任何其他目录一样。git 目录.git不包含任何将存储库绑定到您的驱动器上的特定路径的信息。
2-6.删除和恢复工作目录的内容
问题
您想要验证 git 已经将您的所有文件存储在数据库.git/objects中。为此,您需要删除工作目录的内容,然后从 git 数据库中恢复它。
解决办法
输入在配方 2-3 中创建的储存库:
$ cd git-recipes/02-03
删除所有文件和目录,除了.git子目录:
$ ls -la
$ rm -rf *
$ rm .????*
现在,目录git-recipes/02-03/只包含一个子目录.git。您可以通过以下方式进行检查:
$ ls -l
接下来,执行命令:
$ git reset --hard
所有文件都将被还原。命令:
$ ls -l
现在打印出与图 2-4 中相同的结果。
它是如何工作的
通常,包含 git 存储库的目录由两个区域组成。其中一个已经讨论过了,这个 git 目录命名为.git。另一个叫做工作目录。它们都如图 2-7 所示。
图 2-7 。git 目录和工作目录
工作目录是包含您的工作的临时存储。另一方面,git 目录包含存储项目所有快照的数据库。方法 2-6 应该让你相信你的工作目录的内容可以很容易地从数据库中恢复。
2-7.克隆一个空存储库
问题
您想要从配方 2-3 中创建一个存储库的裸克隆。
解决办法
发出以下命令:
$ cd git-recipes
$ git clone --bare 02-03 02-06
进入02-06/目录并检查其内容:
$ cd 02-06
$ ls -la
上述命令将打印与图 2-6 中的相同的输出。
它是如何工作的
git clone 命令带有一个可选参数--bare。您可以使用--bare参数来创建一个空的存储库。一个裸库 只包含 git 目录的内容。它不包含工作目录。这种类型的存储库仅用于同步目的。我们会在第十章用到。
记住可以用
$ git clone --bare [URL]命令创建空的存储库。这种类型的存储库不包含工作目录。它的内容相当于非裸存储库中的一个.git目录的内容。
2-8.使用 git log 命令浏览历史记录
问题
您希望打印以下关于 jQuery 存储库的信息:
- 存储库中修订的完整列表
- 存储库中最新修订版的简化列表
- 约翰·雷西格的修订清单
- 包含 2012 年最后五次修订的列表
解决办法
进入目录git-recipes/02-01/jquery/:
$ cd git-recipes/02-01/jquery
要打印存储库中修订的完整列表,请执行:
$ git log
您将会看到输出类似于清单 2-4 中的输出。这是可从当前修订访问的修订的完整列表。您可以使用空格键和箭头键滚动输出。按下q退出git log。
***清单 2-4。***git log 命令的输出
commit 18cccd04a6f69018242bce96ef905bc5d3be6ff8
Author: Richard Gibson < richard.gibson@gmail.com >
Date: Mon Apr 29 13:31:59 2013 -0400
Fix #13803: domManip remote-script evaluation per 1.9 (AJAX dataType "script")
commit 55e319aa52eb828a3a4c2298aa75b6d15cfa06f8
Author: Corey Frang <gnarf@gnarf.net>
Date: Wed Apr 24 16:07:15 2013 -0400
Fixes #13815: Ensure each element has its own private data object - Tests by @rwldrn
commit 3a6194076b8b7ab5a9d9f5e6ec602db2ab427d3e
Author: Oleg < markelog@gmail.com >
Date: Wed Apr 24 22:15:41 2013 +0400
Fix #13818: Add wrapMap entry for col element
清单 2-4 中的输出显示了三个版本。第一个修订版的名称是:
18cccd04a6f69018242bce96ef905bc5d3be6ff8
本修订版由 Richard Gibson 于 2013 年 4 月 29 日星期一创建。评论是:
Fix #13803: domManip remote-script evaluation per 1.9 (AJAX dataType "script")
提供修订版包含问题编号 13803 的修复程序的信息。
经过缩短和简化,修订列表可以用列表 2-5 中所示的命令打印出来。
清单 2-5。 产生简化和缩短的日志信息的命令
$ git log --abbrev-commit --abbrev=4 --pretty=oneline -10
它将打印类似于以下内容的输出:
18cc Fix #13803: domManip remote-script...
55e3 Fixes #13815: Ensure each element...
3a619 Fix #13818: Add wrapMap entry for...
78c8 Fix #13819: .parent sort direction...
ad71f Fix #13809: Avoid collisions with...
每一行都涉及一个版本,包含一个简短的缩写名 SHA-1 和注释。如果可能,缩写将缩短为四个字符:
18cc
55e3
78c8
必要时,使用更多字符:
3a619
ad71f
使用-10参数,输出仅限于最后 10 个版本。
清单 2-6 中显示的命令打印了 John Resig 所做的修改。
清单 2-6。 约翰·雷西格撰写的修订版
$ git log --author="John Resig"
列表 2-5 和列表 2-6 中显示的参数可以一起使用。命令:
$ git log --abbrev-commit --abbrev=4 --pretty=oneline -10 --author="John Resig"
以简化形式打印 John Resig 的最后 10 次修订。
在清单 2-7 的中给出了生成 2013 年最后五次修订清单的命令。
***清单 2-7。***2013 年最后五次修订
$ git log --pretty=oneline --since="2012-12-20" --until="2013-01-01" -5
它是如何工作的
git 存储库的历史由一系列修订组成。每个修订都是工作目录在特定时间点的快照。修订存储在.git/objects数据库中。
每个版本都通过其名称来识别。Git 使用一种 SHA-1 算法 来生成名字。因为修订版的 SHA-1 是使用许多不同类型的数据计算的——作者姓名、当前时间戳和快照等——我们可以将它们视为唯一标识符。两个不同的修订版具有相同 SHA-1 的概率非常小,可以忽略不计。事实上,SHA-1 唯一性是最基本的 git 假设之一。正如你将在第十一章中看到的。整个同步过程都依赖于它。
提示用户不能给版本命名。所有的名字都是 git 自动生成的。Git 规则,你可以相信它永远不会为不同的版本生成两个相同的名字。
SHA-1 名称的长度为 20 个字节,因此,它们的十六进制表示形式需要 40 个字符,例如:
18cccd04a6f69018242bce96ef905bc5d3be6ff8
在本书的后面,我们将需要使用这个名字作为参数传递给各种命令;如果是这样的话,就不必使用全部 40 个字符。通常,前七个字符就足够了。名称的最短缩写必须是四个字符长。记住缩写必须是唯一的——如果不是,那么你必须使用更多的字符。
储存在库中的修订列表可以用git log命令打印出来。它的各种选项和开关可用于过滤和重新格式化显示的修订。一个git log命令的输出按照版本创建的时间排序。最新版本显示在输出的顶部。
默认情况下,git log打印当前版本的所有可用版本。
可以用--pretty参数改变输出的格式。可用值及其含义总结在表 2-3 中。
表 2-3 。pretty 参数的值
|
价值
|
描述
|
| --- | --- |
| oneline | 名称和注释打印在一行中。 |
| short | 名称、作者和注释 |
| medium | 与short相同,但增加了修订日期 |
| full | 名称、作者、提交者和注释 |
| fuller | 姓名、作者、作者日期、提交者、提交日期和注释 |
| email | 与电子邮件格式的short相同 |
| raw | 低级修订的信息:名称、树、父修订的名称、作者和带时间戳的提交者 |
| format | 用户定义的格式 |
在表 2-3 中显示的参数可以传递给git log命令,如下所示:
$ git log --pretty=oneline
$ git log --pretty=short
$ git log --pretty=raw
值-–pretty=oneline可以缩短为:
$ git log --oneline
影响格式的其他参数有:
--abbrev-commit—此选项打开缩写。--abbrev=n—该选项设置缩写名称的长度。--decorate—该选项包括每个版本的标签和分支。
最短的缩写必须包含四个字符。因此,--abbrev的最小值是 4:
$ git log --abbrev-commit --abbrev=4
提示参数
--oneline将 SHA-1 缩写为七个字符。
参数--pretty=format允许您定义任意输出的格式。包含占位符的特殊字符串定义了输出。命令:
$ git log --pretty=format:"%an --- %H"
将以如下形式打印输出:
Joe Doe --- 123456...
输出的第一部分(例如,Joe Doe)是作者的名字,而第二部分是修订版的完整 SHA-1。该输出由两个占位符生成:
%an – author's name
%H – full SHA-1 hash
其他有用的占位符包括:
%h: abbreviated commit hash,
%ae: author email,
%ad: author date,
%cn: committer name,
%ce: committer email,
%cd: committer date,
%e: encoding,
%s: subject,
%n: newline.
提示
git log手册中提供了占位符的完整列表。您可以使用git help log命令来访问它。
以下是过滤输出中包含的修订的一些参数:
-n—修订数,例如–7 将 git 日志限制为最近七次修订--since="yyyy-mm-dd"—开始日期--until="yyyy-mm-dd"—完成日期--author="John Doe"—由给定作者提交
提示传递给
--since和--until参数的日期可以用yyyy-mm-dd格式或不太正式的格式设置为--since="1 week ago"、--since="Two months ago"、--until="5 days ago"、--until="7 hours ago"、--until="yesterday"。为了避免输入引号,你也可以用点来表示空格,比如--since=1.week.ago、--since=Two.months.ago、--until=5.days.ago。
2-9.使用 git log 和 shortlog 命令分析存储库
问题
对于 jQuery 项目,您需要以下问题的答案:
- 存储库包含多少修订版?
- 有多少开发人员参与了这个项目?
- 他们在这个项目上工作了多少天?
- 工作目录使用了多少空间?
- git 目录使用了多少空间?
- 工作目录中有多少文件?
解决办法
进入目录git-recipes/02-01/jquery/:
$ cd git-recipes/02-01/jquery
要回答这些问题,执行列表 2-8 到 2-13 中所示的命令。
清单 2-8。 打印存储库中提交数量的命令
$ git log --pretty=oneline | wc -l
清单 2-9。 打印贡献者人数的命令
$ git shortlog -s | wc -l
清单 2-10。 产生贡献天数的命令
$ git log --pretty=format:%cd --date=short | uniq | wc -l
清单 2-11。 返回 git 目录使用的空间量的命令
$ du -h -s .git
清单 2-12。 返回工作目录使用的空间量的命令
$ du -h -s --exclude=.git
清单 2-13。 该命令产生工作目录中文件的数量
$ git ls-files | wc -l
提示 Linux 和 Windows 版本的
du都支持--exclude参数。但是其他一些系统,比如 BSD,使用其他选项。在 BSD 中,用–I选项设置排除(I代表忽略)。
它是如何工作的
使用以下命令可以找到上述问题的答案:
git log
git shortlog
git ls-files
du
wc
uniq
grep
我们已经知道,命令:
$ git log --pretty=oneline
以简化形式打印所有修订的列表,其中每个修订占一行。将列表传送到wc -l:
$ git log --pretty=oneline | wc -l
我们得到修订的数量。
git shortlog命令显示了由作者分组的提交信息。如果没有任何参数,其输出具有以下形式:
Adam Coulombe (1):
Fix #13150, ...
Adam J. Sontag (7):
.closest() should
Add a comment expl
Add a comment to e
Add link to chrome
shorten the SHA
Fix tabs vs spaces
Revert grunt, grun
...
上面的列表包含了所有的开发者和他们的修订版。参数-s仅打印修订号和开发人员姓名:
1 Adam Coulombe
7 Adam J. Sontag
...
参数-n打印按修订次数以数字顺序排序的结果。
为了打印贡献者的数量,我们将git shortlog -s的结果传送到wc -l:
$ git shortlog -s | wc -l
下一个问题有点难回答。首先,我们想以特殊的形式打印出git log命令的输出。我们希望每行只包含一个提交日期,格式为yyyy-mm-dd。这可以通过以下方式实现:
$ git log --pretty=format:%cd --date=short
上述命令将生成日期列表:
2013-04-22
2013-04-22
2013-04-20
2013-04-20
2013-04-18
...
每次提交的日期都会显示在输出中。让我们删除重复的。我们可以使用uniq命令来完成:
$ git log --pretty=format:%cd --date=short | uniq
因此,我们将找出对项目做出贡献的不同日期。如果我们将结果传送到wc -l:
$ git log --pretty=format:%cd --date=short | uniq | wc -l
然后我们得到期望的天数。
提示这是我用来宣布我写一本书的工作天数的大致标准。当命令
$ git log --pretty=format:%cd --date=short | uniq | wc -l返回 95 时,意味着我写了一本书不超过 95 天。
接下来的两个问题涉及工作目录和 git 目录包含的空间量。git 目录包含的空间量由以下命令返回:
$ du -h -s .git
工作目录包含的空间量由以下命令打印:
$ du -h -s --exclude=.git
以下是 jQuery 项目的结果:
- 工作目录:1.3 MB
- git 目录:16 MB
正如您所看到的,git 目录比工作目录使用了更多的空间。这并不奇怪:存储在.git/objects中的数据库包含 5192 个修订。每一次修订都可以看作是一个完整工作目录的快照。
最后一个问题可以用 git ls-files 命令来回答:
$ git ls-files | wc -l
git ls-files 命令打印工作目录中所有文件的名称。我们用wc –l来数它们。
2-10.为配方 2-8 和 2-9 中讨论的命令 定义别名
问题
清单 2-5 和 2-8 到 2-13 中显示的命令输入起来相当长。您希望定义更容易键入的别名,同时返回相同的输出。
解决办法
打开命令行并转到您的主目录。在 Windows 上使用 Linux、Mac 或 bash 命令行,可以通过以下方式完成:
$ cd ∼
如果您使用标准的 Windows 命令行,请尝试:
$ cd %userprofile%
启动文本文件编辑器并打开文件.gitconfig。如果您使用vi,您可以使用:
$ vi .gitconfig
在文件的底部追加清单 2-14 中的内容。保存文件并退出编辑器。
清单 2-14。 清单 2-5 和 2-8 到 2-13 中所示命令的别名
[alias]
l = log --oneline --abbrev-commit --abbrev=4 -25
days = "!days() {
git log --pretty=format:%cd --date=short | uniq;
}; days"
stat = "!stat() {
echo -n Number of revisions:;
git log --oneline | wc -l;
echo -n Number of developers:;
git shortlog -s | wc -l;
echo -n Number of days:;
git days | wc -l;
echo -n The working directory:;
du -h -s --exclude=.git;
echo -n The git directory:;
du -h -s .git;
echo -n Number of files in the working dir:;
git ls-files | wc -l;
}; stat"
提示您不必键入清单 2-14 中显示的别名。它们都可以在
https://github.com/gajdaw-git-recipes/aliases存储库中找到。
注意清单 2-14 中的应该在 stat 和 days 别名中不带换行符。该文件应该类似于:
days = "!days() { ... }; days"
stat = "!stat() { ... }; stat"
换行符仅用于可读性目的。
当你在.gitconfig文件的底部输入完清单 2-14 的内容后,进入目录git-recipes/02-01/jquery/:
$ cd git-recipes/02-01/jquery
并执行第一个别名:
$ git l
您应该会看到类似于以下内容的输出:
18cc Fix #13803: domManip remote-script ...
55e3 Fixes #13815: Ensure each element ...
3a619 Fix #13818: Add wrapMap entry for ...
...
接下来尝试第二个别名:
$ git stat
它将产生类似的结果:
Number of revisions: 5192
Number of developers: 190
Number of days: 1246
The working directory: 1.3M .
The git directory: 16M .git
Number of files in the working dir: 149
注意别名 git stat 使用
echo和–n参数来抑制换行符的输出。如果您的系统不支持echo –n,以上结果的格式会有所不同。
它是如何工作的
Git 允许您为任意命令定义别名。别名应该存储在用户的配置文件中。该文件应该被命名为.gitconfig,并存储在您的主目录中。如果您已经执行了任何命令来配置带有--global选项的 git,例如git config --global user.name,那么.gitconfig文件已经存在于您的主目录中。否则,您必须创建它。
清单 2-14 中显示的第一个别名可以通过以下方式创建:
$ git config --global alias.l "log --oneline --abbrev-commit --abbrev=4 -25"
命令git config –global alias.abc "def"只是在您的个人.gitconfing文件的[alias]部分创建一个条目abc = def。
如果您想找到主目录的位置,请键入:
$ cd ∼
$ pwd
上述命令可以在 Linux、Mac 或 Windows 上的 bash 命令行上很好地工作。如果使用标准的 Windows 命令行,请使用:
$ cd %userprofile%
$ cd
如果您使用vi工作,您可以使用以下命令打开 git 配置文件:
# Linux
$ vi ∼/.gitconfig
# Windows
$ vi %userprofile%\.gitconfig
的语法。gitconfig 文件
我们将从一个.gitconfig文件的语法开始解释别名。有趣的字符有:散列符号、分号、引号和反斜杠。
在一个.gitconfig文件中,你可以使用散列符号和分号来表示延伸到行尾的注释。因此定义是:
word = lorem ; ipsum
用值lorem设置名为word的属性。第二个词ipsum被跳过,因为分号开始注释。类似的规则也适用于哈希标记。定义:
word2 = dolor # sit
用值dolor设置属性word2。
如果要定义包含分号或散列符号的值,必须使用引号:
sentence = "Lorem ; ipsum"
上面定义了一个名为sentence的属性,其值为:
Lorem ; ipsum
引号应该被转义是不足为奇的。该行:
person = "John \"Moo\" Cowboy"
用值定义属性person:
John "Moo" Cowboy
同样的转义过程也适用于反斜杠。定义:
str = "a\\b"
将str属性的值设置为:
a\b
以上描述阐明了以下符号:
something = "x ; \"y\" ; \\ ; z"
因为我们使用分号,所以需要用引号括起来。里面的引号被转义了。双反斜杠是转义反斜杠,因此属性something的值为:
x ; "y" ; \ ; z
请记住,这些规则适用于您存储在.gitconfig文件中的所有内容。
提示
.gitconfig文件的语法在手册的语法:$ git help config 一节中有描述
别名语法
在.gitconfig文件中定义 git 别名的语法是:
alias = command
或者
alias = !command
第一个版本——没有感叹号的版本——适用于 git 子命令。别名:
abc = def
定义可以命名为的命令:
$ git abc
执行时,git abc将产生与以下相同的效果:
$ git def
因此,我们可以定义别名:
l = log --pretty=oneline
当被调用时:
$ git l
将扩展到:
$ git log --pretty=oneline
别名的第二种语法——带感叹号的语法——适用于任意 shell 命令。别名:
list-files = !ls
可以称为:
$ git list-files
该调用将产生以下命令:
$ ls
我在 shell 函数中使用带感叹号前缀的别名。别名:
foo = "!bar(){ }; bar"
可以称为:
$ git foo
感叹号告诉 git 应该将这个别名传递给 shell。下一部分:
bar(){};bar
由函数定义组成:
bar(){};
和一个函数调用:
bar
在大括号内,您可以放置任意数量的完整 shell 调用,用分号分隔,例如:
foo = "!bar(){ echo abc; ls; }; bar"
这个别名可以称为:
$ git foo
这将产生两个命令:
$ echo abc
$ ls
同样,别名:
info = "!funInfo(){ git --version; git log --pretty=oneline -3; }; funInfo"
可以称为:
$ git info
它将产生与两个命令相同的输出:
$ git --version
$ git log --pretty=oneline -3
由于分号的存在,使用 shell 函数的别名需要用引号括起来。
清单 2-14 中的别名
清单 2-14 中的第一个别名是:
l = log --oneline --abbrev-commit --abbrev=4 -25
它没有使用感叹号;因此,它指的是 git 子命令。被呼叫时:
$ git l
它将扩大到:
$ git log --pretty=oneline --abbrev-commit --abbrev=4 -25
Git 允许您向别名传递额外的参数。因此,如果您想生成 John Doe 修订的简化列表,请使用参数--author调用别名:
$ git l --author="John Doe"
您可以用同样的方式传递任何其他参数。
下一个别名是:
days = "!days() {
git log --pretty=format:%cd --date=short | uniq;
}; days"
由于感叹号,它被扩展为一个 shell 命令。该命令定义并调用名为days()的函数。当您键入时:
$ git days
它将最终执行:
$ git log --pretty=format:%cd --date=short | uniq
最后一个别名是一个 shell 函数,它调用许多其他命令。
stat = "!stat() {
echo -n Number of revisions:;
git log --oneline | wc -l;
echo -n Number of developers:;
git shortlog -s | wc -l;
echo -n Number of days:;
git days | wc -l;
echo -n The working directory:;
du -h -s --exclude=.git;
echo -n The git directory:;
du -h -s .git;
echo -n Number of files in the working dir:;
git ls-files | wc -l;
}; stat"
请注意,我们用 subalias 产生天数:
$ git days | wc -l
提示在 git 中创建别名有两种方法。第一种方法在配方 2-10 中讨论,第二种在配方 5-3 中讨论。我更喜欢在里面定义别名。gitconfig 文件,如配方 2-10 所示。这种方法不依赖于用户平台或权限,使得在课堂和培训中更容易采用。
2-11.分析一个流行的存储库
问题
最流行的 Github 库之一是 twitter/bootstrap,可从以下网址获得:
https://github.com/twitter/bootstrap.git
你想用配方 2-10 的别名来分析它。
解决办法
打开命令行并克隆twitter/bootstrap:
$ git clone https://github.com/twitter/bootstrap.git 02-11
命令:
$ git stat
将打印:
Number of revisions: 3569
Number of developers: 259
Number of days: 505
The working directory: 4.9M .
The git directory: 28M .git
Number of files in the working dir: 254
它是如何工作的
使用$ git clone 命令,您可以克隆 Github 或 Bitbucket 上任何可用的公共存储库。在配方 2-10 中创建的别名将有助于您获得项目的一些基本信息。
2-12.可视化存储库的历史
问题
您希望使用一个gitk应用以图形形式显示 HTML 5 样板库的历史。
解决办法
克隆 HTML 5 样板文件库:
$ cd git-recipes
$ git clone https://github.com/h5bp/html5-boilerplate.git 02-12
进入02-12/目录:
$ cd 02-12
并运行gitk命令:
$ gitk
注意如果追加了&符号
$ gitk &,gitk 应用将在后台运行,可以使用命令行执行其他命令。
它是如何工作的
命令$ gitk将显示图 2-8 中所示的窗口。它包含五个面板:
- 修订
- 作者
- 日期
- 修改列表
- 已修改文件的列表
图 2-8 。gitk 应用的主窗口
使用gitk你不仅可以轻松检查修订列表,还可以检查每个修订中引入的修改。
尝试向下滚动修订。你会发现这些修订并不一定形成一个线性的历史。非线性历史如图 2-9 所示。我们将在第五章、第六章和第七章中讨论非线性历史。
图 2-9 。HTML 5 样板库的历史
注意
$ gitk命令接受我们在配方 2-8 中讨论的所有过滤器。例如,您可以使用 gitk 只显示给定作者提交的内容:$ gitk --author=john。
2-13.移除. git 目录
问题
您希望发现删除一个.git目录会如何影响一个存储库。
解决办法
克隆 FontAwesome 存储库:
$ cd git-recipes
$ git clone https://github.com/FortAwesome/Font-Awesome.git 02-13
输入目录:
$ cd 02-13
现在,目录02-13/包含一个 git 存储库。因此,您可以列出日志条目:
$ git log
或项目的贡献者:
$ git shortlog -n -s
如果使用以下命令删除.git目录:
$ rm -rf .git
您将看到工作目录的内容。Git 命令不再起作用了。如果您发出:
$ git log
您将得到以下错误:
fatal: Not a git repository (or any of the parent directories): .git
它是如何工作的
可以用简单的命令删除 git 目录:
$ rm -rf .git
执行此命令后,您将丢失项目的全部历史记录。项目目录将只包含存储在工作目录中的文件的最新版本。
摘要
在这一章中,我们已经讨论了使用 git 库的基本能力。您现在知道如何:
- 克隆一个存储库(远程和本地)。
- 进入存储库并发出各种 git 命令。
- 打印存储在存储库中的列表或修订。
- 用 git log 和 gitk 分析历史。
- 发现存储在存储库中的修订、贡献者和文件的列表和数量。
- 为最常用的命令定义别名。
你也学到了
- git 目录
- git 数据库
- 工作目录
所有这些都是理解后面章节所需要的。
git 目录是一个名为.git的特殊目录,它通常存储在您的项目目录中。它包含您的项目的所有历史和 git 运行所需的各种配置条目。除非得到严格指示,否则不要修改.git目录的内容。
在 git 目录中有一个名为.git/objects的特殊子目录。就是 git 数据库,也叫对象库。这是各种 git 命令存储数据的地方。修订、文件的不同版本、目录、它们的内容等等——它们都存储在这个数据库中。git 不时尝试优化这个数据库。如果使用 git 不当,这可能会导致数据丢失。
第三个区域称为工作目录。它是项目的目录,不包括 git 目录。这是你工作的地方。工作完成后,您可以将工作目录的内容作为下一个版本存储在数据库中。你将在下一章学习如何做到这一点。
你记得秘籍 2-6 吗?如果没有,再分析一遍。这个菜谱展示了使用 git 的一个非常重要的方面。您存储在数据库中的修订可以检索到工作目录中。我们使用git reset --hard来恢复被删除的文件。从现在开始,您应该将工作目录视为一个临时存储。
工作目录和 git 目录用于将每个存储库分类为非裸或裸。
一个非裸存储库包含工作目录和 git 目录。非裸存储库的配置文件.git/config包含以下条目:
[core]
bare = false
裸存储库只包含.git目录。它的.git/config文件包含:
[core]
bare = true
正如你将在第十章中看到的,这种类型的库用于同步目的。
三、使用线性历史创建本地存储库
在这一章中,你将学习如何创建你自己的库,以及如何在你的日常工作中使用它们。这包括
- 用
$ git init初始化新的储存库 - 使用
$ git add –A和$ git commit -m "..."将快照存储为修订版 - 使用
$ git status -s -b检查储存库的状态
您将学习如何从头开始一个新项目,以及如何导入现有文件。
三种方法将集中在将工作目录恢复到存储在修订版中的快照。在学习了这些方法之后,您应该能够将任何存储库(比如 jQuery)的工作目录重置为任意修订版,比如存储库中的第一个修订版,然后将工作目录返回到其最新状态。
我特别关注可能会引发问题的情况。两种方法精确地描述了如何以及何时丢失未提交或已提交的修改。这些知识会建立你的自信。如果您遵守一些简单的规则,您将永远不会丢失存储在 git 存储库中的内容。
在第二章中,我们将存储库描述为非裸露或者裸露。这种特征基于工作目录的存在。这里我们再介绍另一种分类:干净或者脏。这个分类只适用于非裸库。当存储库的工作目录内容与存储在其最新版本中的快照相同时,存储库就是干净的。另一方面,如果工作目录中的文件被修改并且没有提交,我们称这个存储库为脏的。为了找出一个存储库是干净的还是脏的,我们使用了$ git status命令。
3-1.创建您的第一个存储库
问题
您想要开始一个新项目,该项目将由包含您最喜欢的作家所写的书籍列表的文本文件组成。让我们假设您计划将每个作家的作品存储在一个单独的文件中。创建文件并键入其内容后,您应该保存文件并将其提交到存储库中。假设你用阿加莎·克里斯蒂、约翰·格里森姆和斯蒂芬·金的作品创建文件。你的存储库的历史看起来类似于图 3-1 。
图 3-1 。配方 3-1 中的储存库
解决办法
在这个菜谱中,您将使用 git commit 命令创建您的第一个修订。Git 不允许提交,除非您将您的身份存储在配置文件中。如果到目前为止您还没有这样做,请运行以下两个命令,用您的个人信息替换John Doe和john.doe@example.net:
$ git config --global user.name "John Doe"
$ git config --global user.email john.doe@example.net
当您准备好提交时,初始化一个新的存储库:
$ cd git-recipes
$ git init 03-01
$ cd 03-01
现在,目录 03-01 包含了 git 存储库。要进行验证,请运行命令:
$ ls -la
它将打印三个项目:
.
..
.git
正如您所猜测的,存储库是空的。这意味着数据库不包含修订。我们可以用$ git log和$ git status命令来验证这一点。首先,打印历史记录:
$ git log
答案将是:
fatal: bad default revision 'HEAD'
现在,使用以下命令检查状态:
$ git status
git status 打印的信息将是:
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)
注释# Initial commit意味着存储库已经准备好存储第一次提交。就这么办吧。
创建第一个文件:
$ vi agatha-christie.txt
该文件可以包含清单 3-1 中的文本,但这并不重要。
清单 3-1。 阿加莎-克里斯蒂的内容. txt
Novels
1943 | Five Little Pigs
1934 | Murder on the Orient Express
保存文件agatha-christie.txt后,使用以下命令检查存储库的状态:
$ git status -s
您将看到以下输出:
?? agatha-christie.txt
两个问号??通知您agatha-christie.txt文件未被跟踪。这是一个尚未提交的新文件。现在,仓库是脏的。
使用以下两个命令创建您的第一个修订版:
$ git add -A
$ git commit -m "First revision [Agatha Christie]"
该文件现在存储在新版本中。命令:
$ git status -s
返回空输出,这意味着工作目录中没有挂起的更改。换句话说,仓库是干净的。让我们用$ git log检查日志。输出将类似于:
commit de3680b0a770dd46ede81f46cba0ae32f9e4687c
Author: Włodzimierz Gajda <gajdaw@gajdaw.pl>
Date: Thu May 2 12:50:19 2013 +0200
First commit [Agatha Christie]
储存库的当前状态如图 3-2 所示。
图 3-2 。第一次修订后配方 3-1 的储存库
让我们创建第二个修订版。请遵循以下步骤:
-
创建文件
john-grisham.txt$ vi john-grisham.txt -
键入文件的内容:
Novels 1989 | A Time to Kill 1991 | The Firm 1992 | The Pelican Brief -
保存文件并关闭编辑器。
-
Check the status of the repository:
$ git status -s输出:
?? john-grisham.txt通知您一个新的、未被跟踪的文件:
john-grisham.txt -
将工作目录的当前状态保存为新版本:
$ git add -A$ git commit -m "Second revision [John Grisham]" -
Check the status of the repository with:
$ git status -s空输出证明存储库是干净的。
-
Check the log of the repository with the alias from Recipe 2-10:
$ git l输出包含两个修订:
0468 Second revision: [John Grisham]de36 First commit [Agatha Christie] -
存储库应该看起来像图 3-3 。
图 3-3 。第二次修订后配方 3-1 的储存库
完成配方,创建第三个文件stephen-king.txt和第三个版本。程序如下:
-
创建文件
stephen-king.txt$ vi stephen-king.txt -
输入内容:
Novels 1974 | Carrie 1975 | Salem’s Lot 1977 | The Shining -
保存文件并关闭编辑器。
-
Check the status of the repository:
$ git status -s存储库不干净:
?? stephen-king.txt -
为工作目录的当前状态创建修订:
$ git add -A $ git commit -m "Third revision [Stephen King]" -
Check the status of the repository:
$ git status -s现在,输出是空的;因此,我们知道存储库是干净的。
-
Check the log with:
$ git l输出将包含三个修订:
ffa6 Third revision [Stephen King] 0468 Second revision: [John Grisham] de36 First commit [Agatha Christie] -
储存库如图 3-1 所示。
注意配方 3-1 中介绍的两个命令:
$ git add –A和$ git commit -m "..."将你的工作目录的当前状态保存为一个新的版本。我们用它们在每个版本中存储一个新文件,但这不是必须的。您可以创建、删除、移动和复制任意数量的文件。这两个命令存储工作目录,不管修改了多少文件或者使用了哪种类型的工具。
它是如何工作的
使用以下命令初始化新的存储库:
$ git init
您可以传递一个路径来告诉 git 您希望项目存储在哪里。命令:
$ git init 03-01
创建一个新的空目录03-01并初始化其中的一个空存储库。没有任何参数,$ git init命令将在当前目录中初始化一个新的存储库。
当存储库初始化后,您就可以处理您的项目了:您可以创建文件并输入它们的内容。Git 非常智能地跟踪您在工作目录中所做的更改。它知道你做的所有修改。如果您怀疑这一点,请尝试使用以下命令:
$ git status
它返回关于工作目录中引入的更改的确切信息。这个命令的缩写形式也非常有用。如清单 3-2 所示。
清单 3-2。 回答问题的命令:仓库是脏的还是干净的?
$ git status -s
提示仓库可以被描述为干净或者肮脏。当我们说库是干净的;这意味着工作目录中的所有文件都存储在最新版本中。在这种状态下,命令:
$ git status -s返回一个空结果。存储库是脏的意味着工作目录包含未提交的修改。命令:$ git status -s返回挂起变更的列表。
清单 3-2 中的命令以非常紧凑的形式打印出修改列表。您可以将它视为对以下问题的快速回答:存储库干净吗?如果输出为空,那么存储库是干净的。否则存储库是脏的,输出会列出修改。
在某些时候,您需要决定将工作目录的当前状态保存为一个新的版本。为此,使用清单 3-3 中的两个命令。
清单 3-3。 两个命令将工作目录的当前状态保存为新的版本
$ git add -A
$ git commit -m "Comment..."
现在,将它们视为一个原子操作。我们将在第四章中讨论他们的确切角色。现在,知道当执行时,它们将创建一个新的修订,并使存储库保持干净状态就足够了。
如果你是新的 git 用户,我建议在学习的早期阶段,你应该在每次修订后检查库的状态和日志。众所周知,这可以通过以下方式实现:
$ git status -s
$ git log
您也可以使用配方 2-10 中定义的$ git l别名。
3-2.创建 git 快照别名
问题
正如你已经知道的,你的工作目录的快照可以用清单 3-3 中的两个命令保存。因为我们将其视为单个操作,所以您希望定义将执行这两个命令的别名快照。您的别名,执行时为:
$ git snapshot
应该将工作目录的当前状态存储为新版本。
解决办法
打开命令行,转到您的主目录,编辑您的.gitconfig文件。遵循配方 2-10 开头给出的程序。
在您的.gitconfig文件的[alias]部分的末尾键入清单 3-4 的内容,保存文件,并关闭编辑器。
清单 3-4。 别名饭桶快照
[alias]
snapshot = "!snapshot() {
COMMENT=wip;
if [ \"$*\" ]; then
COMMENT=\"$*\";
fi;
git add -A;
git commit -m \"$COMMENT\";
}; snapshot"
它是如何工作的
类似于配方 2-10,别名用换行符分开。请记住,这里的换行符只是为了使别名更容易阅读——您必须在您的.gitconfig文件中将别名作为一个长行键入。
别名使用外壳函数snapshot,该函数在解析.gitconfig后变成:
snapshot() {
COMMENT=wip;
if [ "$*" ]; then
COMMENT="$*";
fi;
git add -A;
git commit -m "$COMMENT";
}
说明:
COMMENT=wip;
用值wip定义一个名为COMMENT的变量。Wip 是在制品的缩写。特殊变量$*包含传递给脚本的所有参数。考虑以下命令:
$ some-script a b c
这个调用向脚本some-script发送三个参数:a、b和c。您可以使用带引号的$*变量"$*"来访问所有三个参数。
条件语句 if-then-fi:
if [ "$*" ]; then
COMMENT="$*";
fi;
检查传递给脚本的参数。如果用参数调用脚本,它们将被分配给COMMENT变量。否则COMMENT变量将保持不变——它存储默认值 wip。
现在,您已经了解了将使您能够理解快照别名如何工作的一切。当我们运行该命令时:
$ git snapshot
它创建一个带有注释 wip 的版本。
如果我们传递任何参数:
$ git snapshot Lorem ipsum dolor
然后别名将创建带有注释“Lorem ipsum dolor”的修订。
3-3.在日常工作中使用 git 快照别名
问题
您想要启动一个新项目,该项目将由存储儿童歌曲的文本文件组成。类似于方法 3-1,您计划在新提交中保存每个新文件。为了避免同时键入$ git add和$ git commit命令,您更喜欢使用$ git snapshot别名,在配方 3-2 中定义。
解决办法
初始化新的存储库:
$ cd git-recipes
$ mkdir 03-03
$ cd 03-03
$ git init
创建包含“唱一首六便士的歌”的歌词的第一个修订版。
-
创建文件
sing-a-song-of-sixpence.txt$ vi sing-a-song-of-sixpence.txt -
键入文件的内容:
Sing a song of sixpence, A pocket full of rye. Four and twenty blackbirds, Baked in a pie. ... -
保存文件并关闭编辑器。
-
Check the status of the repository with
$ git status -s存储库不干净。
-
将工作目录的当前状态保存为新版本:
$ git snapshot Sing a song of sixpence -
Check the status of the repository with
$ git status -s仓库是干净的。
-
Check the log of the repository with
$ git l输出将包含一个版本:
7cfb Sing a song of sixpence
存储库现在看起来像图 3-4 。
图 3-4 。“儿童歌曲”项目第一次改版
创建包含“咩,咩,黑羊”歌曲歌词的第二个修订版。
-
创建
baa-baa-black-sheep.txt文件:$ vi baa-baa-black-sheep.txt -
键入文件的内容:
Baa, baa, black sheep, Have you any wool? Yes, sir, yes, sir, Three bags full; ... -
保存文件并关闭编辑器。
-
Check the status of the repository with
$ git status -s存储库不干净。
-
将工作目录的当前状态保存为新版本:
$ git snapshot Baa, baa black sheep -
Check the status of the repository with
$ git status -s仓库是干净的。
-
Check the log of the repository with
$ git l输出将包含两个修订:
564f Baa, baa black sheep 7cfb Sing a song of sixpence
“儿童歌曲”项目的现状如图 3-5 所示。
图 3-5 。第二次修订后的“儿童歌曲”项目
现在你决定这个项目应该存储不同语言的歌曲。创建一个名为EN的新目录,并将两个文件移动到其中:
$ mkdir EN
$ mv *. txt EN
检查存储库的状态:
$ git status -s
存储库不干净。将工作目录的当前状态保存为新版本:
$ git snapshot Internationalization: directory EN
现在存储库是干净的。命令:
$ git l
返回三个版本:
f305 Internationalization: directory EN
564f Baa, baa black sheep
7cfb Sing a song of sixpence
我们得到的知识库如图 3-6 所示。
图 3-6 。第三次改版后的“儿童歌曲”项目
创建新文件夹PL:
$ mkdir PL
现在,检查状态:
$ git status -s
多奇怪,储存库是干净的!这是因为 git 不跟踪空目录。
提示绕过 git 禁止提交空目录的限制的公认方法是创建一个名为
.gitkeep的空文件。
现在准备包含波兰歌曲“Bajka iskierki”的修订版:
-
创建
bajka-iskierki.txt文件:$ vi PL/bajka-iskierki.txt -
键入文件的内容:
Na Wojtusia z popielnika Iskiereczka mruga.... -
保存文件并关闭编辑器。
-
Check the status of the repository:
$ git status -s存储库不干净。
-
将工作目录的当前状态保存为新版本:
$ git snapshot [PL] Bajka iskierki -
Check the status of the repository with:
$ git status –s仓库是干净的。
-
Check the log of the repository with:
$ git l输出将包含四个修订:
d234 [PL] Bajka iskierki f305 Internationalization: directory EN 564f Baa, baa black sheep 7cfb Sing a song of sixpence
储存库的状态如图 3-7 所示。
图 3-7 。第四次改版后的“儿童歌曲”项目
现在你决定每首歌都要以一句歌词开头:
TITLE: Abc...
打开EN/sing-a-song-of-sixpence.txt文件$ vi EN/sing-a-song-of-sixpence.txt,在文件的最开头插入一行:
TITLE: Sing a song of sixpence
保存文件并关闭编辑器。
以同样的方式修改第二个文件baa-baa-black-sheep.txt。第一行应该包含TITLE: Baa, baa, black sheep。最后修改第三个文件bajka-iskierki.txt。输入文本TITLE: Bajka iskierki。保存文件并关闭编辑器。
好了,现在三个文件都被修改了。命令$ git status –s打印:
M EN/baa-baa-black-sheep.txt
M EN/sing-a-song-of-sixpence.txt
M PL/bajka-iskierki.txt
创建将存储项目当前状态的修订:
$ git snapshot Titles
由$ git l打印的历史现在打印五个修订:
39d6 Titles
d234 [PL] Bajka iskierki
f305 Internationalization: directory EN
564f Baa, baa black sheep
7cfb Sing a song of sixpence
如你所见,单个修订可以存储任意数量的修改。我们创建的最后一个修订版包括三个修改过的文件。配方 3-3 的最终储存库显示在图 3-8 中。
图 3-8 。配方 3-3 的最终储存库
它是如何工作的
您已经知道如何用$ git init初始化一个新项目。当不带参数执行时,该命令将在当前目录中创建一个存储库。
项目初始化后,您可以使用以下命令继续工作:
$ git status -s
和别名:
$ git snapshot
$ git l
每当您想要将工作目录的当前状态保存为修订时,请使用以下命令:
$ git snapshot
如果要设置修订的注释,请使用参数:
$ git snapshot A short info explaining the purpose of the revision
3-4.映射名称
问题
假设在第一次接触 git 时,您以这样的方式配置它,您的名字被设置为johny:
$ git config --global user.name johny
你在你的项目上工作了一段时间,然后你决定你更喜欢被称为John Doe。又过了一段时间,在这段时间里,你会再次改变主意。这次你要叫Paul "Moo" Cowboy。因此,你的修改被分配给三个不同的作者:johny、John Doe和Paul "Moo" Cowboy。您希望重新配置您的存储库,使所有这些名称都映射到您的真实名称。你可以通过准备一个.mailmap文件来达到这个效果。
解决办法
从配方 3-3 克隆存储库:
$ cd git-recipes
$ git clone 03-03 03-04
$ cd 03-04
检查修订的作者:
$ git shortlog -s
输出将类似于:
5 Włodzimierz Gajda
当然,我的名字会被你的代替。该输出通知您名为“odzimierz Gajda”的人编写了五个修订。
打开您的.gitconfig文件,并将您的名称改为:
[user]
name = johny
email = john.doe@example.net
接下来创建修订版本为johny。遵循程序:
-
创建目录
FR/和文件FR/alouette-gentille-alouette.txt:$ mkdir FR $ vi FR/alouette-gentille-alouette.txt -
键入文件的内容:
Alouette, gentille alouette, Alouette, je te plumerai. ... -
保存文件并关闭编辑器。
-
创建修订:
$ git snapshot [FR] Alouette, gentille alouette
现在,$ git shortlog -s产生的输出将包括两个作者:
5 Włodzimierz Gajda
1 johny
遵循相同的程序,通过John Doe创建新版本:
-
把你在
.gitconfig的名字改成无名氏:[user] name = John Doe email = john.doe@example.net -
创建一个新文件
little-skylark.txt:$ vi EN/little-skylark.txt -
键入文件的内容:
Little skylark, lovely little skylark, Little lark, I'll pluck your feathers off. ... -
保存文件并关闭编辑器。
-
将工作目录的当前状态保存为新版本:
$ git snapshot [EN] Little skylark, lovely little skylark -
Check the list of authors with:
$ git shortlog -s -n由于有了
-n选项,输出将按照修订数量降序排列:5 Włodzimierz Gajda 1 John Doe 1 johny接下来,在名称
Paul "Moo" Cowboy下创建修订:-
Change your name in
.gitconfig:[user] name = "Paul \"Moo\" Cowboy" email = moo@wild-west.example.net请注意,您必须用反斜杠对引号进行转义。
-
创建一个新文件
frere-jacques.txt:$ vi FR/frere-jacques.txt -
键入文件的内容:
Frère Jacques, frère Jacques, Dormez-vous? Dormez-vous? Sonnez les matines! Sonnez les matines! Ding, daing, dong. Ding, daing, dong. -
保存文件并关闭编辑器。
-
将工作目录的当前状态保存为新版本:
$ git snapshot [FR] Frere Jacques -
Check the list of authors with:
$ git shortlog -s -n输出将包含四个条目:
5 Włodzimierz Gajda 1 John Doe 1 Paul "Moo" Cowboy 1 johny
-
提示我们现在有四个不同的作者,我们将继续邮件映射。然而,当存储库包含由以下作者创作的修订时,我鼓励您检查由
$ git shortlog -s -n返回的结果:name = "Paul "Moo" Cowboy", name = Peter ;Moo Cowboy, name = "Peter ;Moo Cowboy", name = "Peter "Moo" Cowboy"。这些例子有助于理解.gitconfig文件的解析。理解引用和评论的处理特别有帮助。
要继续映射姓名和电子邮件,请将您在.gitconfig文件中的姓名和电子邮件恢复为原始数据。我会输入:
[user]
name = Włodzimierz Gajda
email = gajdaw@gajdaw.pl
你必须用你的名字和电子邮件替换上述数据。
现在,创建文件.mailmap:
$ vi .mailmap
记住,.mailmap文件必须存储在当前项目的工作目录的根目录下。否则没有效果。键入以下内容(用您自己的电子邮件替换我的电子邮件):
My New Extra Name < gajdaw@gajdaw.pl >
当你保存.mailmap文件时,命令:
$ git shortlog -s -n
它将返回:
5 My New Extra Name
1 John Doe
1 Paul "Moo" Cowboy
1 johny
你可以看到,我的名字是从Włodzimierz Gajda映射到My New Extra Name的。再次打开.mailmap文件并追加另一行:
John Doe < john.doe@example.net >
该行:
John Doe < john.doe@example.net >
将所有提交的名称从john.doe@example.net更改为John Doe。的输出:
$ git shortlog -sn
现在是:
5 My New Extra Name
2 John Doe
1 Paul "Moo" Cowboy
以前分配给 johny 的提交现在被视为由John Doe创建。
如何将Paul "Moo" Cowboy的修订分配给John Doe?您将通过以下.mailmap条目实现它:
John Doe <john.doe@example.net> <moo@wild-west.example.net>
上面的条目映射了从moo@wild-west.example.net到John Doe的所有修订。现在,$ git shortlog -ns的产量是:
5 My New Extra Name
3 John Doe
完成配方,创建.mailmap条目,将所有修订重新分配给你。通过添加您的第二个名字来重新映射您的姓名。这个问题的解决方案显示在清单 3-5 中。
清单 3-5。 这个。邮件映射内容将所有修订重新分配给我,并通过插入我的第二个名字 Edmund 来更改我的姓名
Włodzimierz Edmund Gajda <gajdaw@gajdaw.pl>
Włodzimierz Edmund Gajda <gajdaw@gajdaw.pl> <john.doe@example.net>
Włodzimierz Edmund Gajda <gajdaw@gajdaw.pl> <moo@wild-west.example.net>
现在,$ git shortlog -ns命令返回:
8 Włodzimierz Edmund Gajda
所有的修改都是由一个人完成的——我。
完成制作方法,创建新版本:
$ git snapshot Mapping names with .mailmap
现在,存储库包含九个修订,只有一个提交者。您可以通过以下方式进行验证:
$ git stat
这是我们在配方 2-10 中创建的别名。
记住.mailmap文件只影响$ git shortlog命令。命令:
$ git log
将打印:
commit abda33b8addab96e2016f974765f937f9dac6e3c
Author: Włodzimierz Gajda <gajdaw@gajdaw.pl>
Date: Thu May 9 10:35:15 2013 +0200
Mapping names with .mailmap
commit ba805256075eb86cf8a09a1d5c3161dbe6fc63e5
Author: Paul "Moo" Cowboy <moo@wild-west.example.net>
Date: Thu May 9 10:07:01 2013 +0200
[FR] Frere Jacques
commit 659ca289a3898eaf210d0c68228a645a74a3dd52
Author: John Doe <john.doe@example.net>
Date: Thu May 9 10:01:44 2013 +0200
[EN] Little skylark, lovely little skylark
...
在内部,所有的修改都用原作者标明。
它是如何工作的
要更改您的姓名,您可以使用以下命令:
$ git config --global user.name "Your Name"
或者手动编辑您的.gitconfig文件。这些命令:
$ git config --global user.name "John Doe"
$ git config --global user.email john.doe@example.net
创建以下.gitconfig条目:
[user]
name = John Doe
email = john.doe@example.net
使用$ git config命令还是编辑.gitconfig文件并不重要。需要记住的重要事实是,当您使用$ git commit命令创建修订时,会用到来自.gitconfig的姓名和电子邮件条目。因此,如果您首先将您的姓名定义为:
[user]
name = johny
email = john.doe@example.net
后来决定将其更改为:
[user]
name = John Doe
email = john@doe.example.com
那么您的存储库的历史将包含两个作者:johny和John Doe。命令:
$ git shortlog -s
将返回这两个名称,因为它们被视为不同的人:
13 johny
8 John Doe
这个输出告诉我们,johnny 编写了 13 个修订,John Doe 编写了 8 个。你可以提供额外的信息,说明 johny 和 John Doe 实际上指的是同一个人。这个映射应该存储在工作目录的.mailmap文件中。文件的每一行都定义了名字到名字或者电子邮件到电子邮件的映射。该行:
Proper Name < commit@example.net >
定义将作者的电子邮件设置为commit@example.net的提交应该用名称Proper Name标记。
该行:
< proper@example.net > < commit@example.net >
重新映射电子邮件。它规定由commit@example.net创作的修改应该分配给使用电子邮件proper@example.net的人。
更复杂的例子:
Proper Name < proper@example.net > < commit@example.net >
将提交的名称和电子邮件都更改为作者的电子邮件等于commit@example.net到Proper Name和proper@example.net。
可以将.mailmap文件的位置从工作目录的根目录更改为由mailmap.file配置选项定义的任意位置。.gitconfig和.mailmap文件都可以是 utf-8 编码的,因此你可以在里面使用非 ascii 字符,比如,。如果你想分析现实生活中的.mailmap例子,请访问 jQuery 资源库:https://github.com/jquery/jquery。
文档
.mailmap文件的完整规范可在 shortlog 命令: $ git shortlog --help的手册中找到。
请记住,当您定义一个奇怪的名称时,例如:
Paul "Moo" Cowboy
你必须使用引号和反斜杠:
[user]
name = "Paul \"Moo\" Cowboy"
email = john.doe@example.net
提示命令:
$ git log --pretty=format:"- { name: '%an', email: '%ae' }" | sort | uniq以 YAML 格式打印所有作者的完整列表。您可以使用这个列表为大型项目自动生成您的.mailmap文件。
3-5.使用 git 重置恢复修订
问题
您可能还记得,git 是一个版本控制系统。这意味着它存储项目中文件的所有版本。您可能想知道如何访问不久前存储的版本?您希望将项目恢复到第一个修订版,然后恢复到一段时间前提交的修订版,最后恢复到最后一个修订版。
解决办法
从配方 3-4 克隆存储库:
$ cd git-recipes
$ git clone 03-04 03-05
$ cd 03-05
并用$ git l打印历史。输出将类似于清单 3-6 。保存$ git l的输出,以备将来参考使用该配方。我将参考清单 3-6 ,但是因为你有不同的 SHA-1 散列,你将需要你自己的历史副本。一旦你学会了如何使用 reflog,保存就没有必要了。你将在配方 3-8 中学习如何使用 reflog。
清单 3-6。 储存库使用的历史菜谱 3-5
abda Mapping names with .mailmap
ba80 [FR] Frere Jacques
659c [EN] Little skylark, lovely little skylark
348f [FR] Alouette, gentille alouette
39d6 Titles
d234 [PL] Bajka iskierki
f305 Internationalization: directory EN
564f Baa, baa black sheep
7cfb Sing a song of sixpence
现在,将工作目录恢复到名为7cfb的第一个版本:
$ git reset --hard 7cfb
成功执行上述命令后,工作目录包含一个文件sing-a-song-of-sixpence.txt。您可以使用$ ls命令来验证它。此外,您可以通过$ git l查看历史记录。输出将只包含一个版本:
7cfb Sing a song of sixpence
这就是为什么我希望你复制清单 3-6 中的$ git l命令的输出。所有修订都包含在数据库中,但它们现在不包含在历史记录中。只有知道它们的 SHA-1 名称,才能恢复它们。如果你不知道它们的名字,你可以使用 ref log——我们将在秘籍 3-7 中学习。存储库现在看起来像图 3-4 。历史上除了7cfb没有其他版本。
我假设你知道清单 3-6 中打印的修订名称。如果没有,再次启动配方,这次保存清单 3-6 中显示的历史。
现在,恢复修订版,表示为:
f305 Internationalization: directory EN
您可以使用以下命令来完成:
$ git reset --hard f305
在该命令之后,工作目录包含以下目录和文件:
.
`-- EN
|-- baa-baa-black-sheep.txt
`-- sing-a-song-of-sixpence.txt
存储库看起来像图 3-6 。命令$ git l打印三个版本:
f305 Internationalization: directory EN
564f Baa, baa black sheep
7cfb Sing a song of sixpence
最后,将您的存储库重置为最新版本,如清单 3-6 所示:
$ git reset --hard abda
命令$ git l打印出与清单 3-6 中的相同的结果。工作目录包含配方 3-3 和 3-4 中创建的所有文件。
小心配方 3-5 清楚地表明,存储在
.git/objects中的数据库和存储库的历史不是一回事。在执行$ git reset命令后,一些修订从历史记录中删除,但它们仍然存在于数据库中。存储库的历史只是数据库中所有可用信息的子集。要从数据库中获取信息,您需要一个有效的名称。
它是如何工作的
存储库的历史可以显示为修订列表。我们可以使用$ git log --pretty=oneline或者别名$ git l。正如您已经知道的,每个修订都有其唯一的名称。要将工作目录恢复到您可以使用的修订之一:
$ git reset --hard [REVISION]
该命令执行以下两项操作:
- 它将工作目录的状态重置为指定的修订版,这意味着所有文件和目录的内容都将恢复到与修订版中保存的快照完全相同的快照。
- 它从历史记录中删除在指定版本之后创建的所有版本。
如果您想恢复存储库的原始状态,就像在使用$ git reset命令之前一样,您必须记住最新版本的名称,或者您可以使用 reflog。
3-6.用 git 检验恢复修订版
问题
将工作目录恢复到给定版本的操作可以通过$ git reset或$ git checkout命令来执行。在方法 3-5 中,你用$ git reset命令恢复旧的快照。现在,您想用一个$ git checkout命令获得类似的结果。
解决办法
从配方 3-4 克隆存储库:
$ cd git-recipes
$ git clone 03-04 03-06
$ cd 03-06
并用$ git l打印历史。输出将与清单 3-6 中的相同。保存$ git l的输出以备将来参考。
现在,将工作目录恢复到第一个版本,名为7cfb:
$ git checkout 7cfb
该命令将存储库的状态更改为分离头。您可以通过以下方式验证这一点:
$ git status -sb
输出应该是:
## HEAD (no branch)
工作目录现在包含一个文件sing-a-song-of-sixpence.txt,打印有$ git l的历史仅包含一个版本:
7cfb Sing a song of sixpence
使用以下命令将其恢复到正常状态:
$ git checkout master
现在用$ git l打印的历史包含了清单 3-6 中显示的所有修订。
您可以再次使用$ git checkout切换到其他版本,例如:
$ git checkout 564f
要返回正常状态,请使用:
$ git checkout master
它是如何工作的
恢复以前保存的快照的第二种方法是使用以下命令:
$ git checkout [REVISION]
该命令的工作方式与配方 3-5 中讨论的$ git reset不同。
$ git checkout命令执行以下三个操作:
- 它进入分离的头部状态。
- 它将工作目录的状态重置为指定的版本。
- 它从历史记录中删除在指定修订之后创建的所有修订。
分离的头是存储库的一种特殊状态,在这种状态下,您不在任何分支上。我们将在第五章、第六章、第七章和第十章更详细地讨论分支。现在,要使用$ git checkout,你只需要知道:
$ git checkout [SHA-1]命令进入分离头状态。- 如何检查您的存储库的状态。
- 如何从脱离的头部回到正常状态?
命令:
$ git status -b
返回当前分支的信息。它输出:
# Not currently on any branch.
当您处于分离的头部状态或:
# On branch master
当你处于正常状态时。你可以连接一个$ git status命令的两个有用的开关-s和-b:
$ git status -s -b
或者甚至:
$ git status -sb
当存储库干净并处于分离磁头模式时,此命令会打印:
## HEAD (no branch)
在正常状态下,输出为:
#master
如果您处于分离状态,您可以通过以下方式返回正常状态:
$ git checkout master
总而言之,该命令:
$ git checkout [REVISION]
将工作目录恢复到指定版本,并进入分离头状态。
命令:
$ git checkout master
返回到最新版本并恢复正常状态。
提示配方 3-6 介绍了一个存储库的新特性。我们可以说存储库处于脱离头状态或正常状态。
3-7.创建 git 的别名
问题
如何定义一个别名来简化一个$ git status –sb命令的执行?
解决办法
在您的.gitconfig文件的[alias]部分的末尾键入清单 3-7 的内容。
清单 3-7。 别名饭桶年代
[alias]
s = status -sb
您可以使用以下命令获得相同的结果:
$ git config --global alias.s "status -sb"
它是如何工作的
别名$ git s执行命令:
$ git status -sb
输出传达了以下问题的答案:
- 存储库是干净的还是脏的?换句话说,是否有任何未提交的更改?
- 存储库是否处于分离状态?或者我们在一根树枝上?如果是,打印分行的名称。
3-8.使用参考日志
问题
在配方 3-5 和 3-6 中,用 SHA-1 名称保存日志的程序非常麻烦。如果你知道如何使用 reflog,这是可以避免的。你将想要创建一个如图 3-9 所示的库。接下来,您希望使用$ git reset --hard [REVISION]命令将工作目录恢复到每次提交。您可能更喜欢使用 reflog,而不是复制和粘贴 SHA-1 名称。
图 3-9 。配方 3-8 中讨论的储存库
提示图 3-9 中显示的文件内容并不重要。
解决办法
初始化新的存储库:
$ cd git-recipes
$ git init 03-08
$ cd 03-08
使用以下内容创建第一个文件:
$ echo lorem > lorem.txt
该命令创建一个名为lorem.txt的新文件。该文件包含一个单词lorem。您可以使用两个命令来验证它:
$ ls
$ cat lorem.txt
第一个文件列出了当前目录的内容(该列表将由一个文件lorem.txt组成),第二个文件显示了lorem.txt的内容(当然是lorem)。
现在用以下内容创建第一个修订版:
$ git snapshot lorem
当然,存储库是干净的,$ git status –s返回空结果。现在使用以下命令检查参考日志:
$ git reflog
该命令的输出如清单 3-8 中的所示。
清单 3-8。 第一次提交后 git reflog 的输出
bb057dd HEAD@{0}: commit (initial): lorem
它通知您,带有注释lorem的修订现在可以称为:
bb057dd
或者
HEAD@{0 }.
让我们创建第二个版本:
$ echo ipsum > ipsum.txt
$ git snapshot ipsum
目前,存储库包含两个修订版本,注释分别为lorem和ipsum。命令:
$ git reflog
现在返回清单 3-9 中的输出。
清单 3-9。 第二次提交后 git reflog 的输出
227c9fb HEAD@{0}: commit: ipsum
bb057dd HEAD@{1}: commit (initial): lorem
发生了什么事?我们在历史中从版本lorem移动到版本ipsum。当前版本可以被称为HEAD@{0}——现在是版本ipsum。之前的版本——也就是lorem——可以称为HEAD@{1}。
创建第三个版本:
$ echo dolor > dolor.txt
$ git snapshot dolor
并执行:
$ git reflog
输出显示在清单 3-10 中。
清单 3-10。 第三次修改后 git reflog 的输出
fe7dbef HEAD@{0}: commit: dolor
227c9fb HEAD@{1}: commit: ipsum
bb057dd HEAD@{2}: commit (initial): lorem
历史向前发展。这次HEAD@{0}指的是dolor修改。之前的版本是ipsum,所以可以简称为HEAD@{1}。我们创建的第一个版本——lorem——现在可以作为HEAD@{2}使用。
是时候使用引用日志名称来恢复修订了。首先,我们想恢复标题为lorem的版本。您可以使用以下命令来完成此操作:
$ git reset --hard HEAD@{2}
之后,工作目录应该只包含一个文件,$ git reflog命令应该返回清单 3-11 中的输出。
***清单 3-11。***git 复位后 git reflog 的输出——硬磁头@{2}
bb057dd HEAD@{0}: reset: moving to HEAD@{2}
fe7dbef HEAD@{1}: commit: dolor
227c9fb HEAD@{2}: commit: ipsum
bb057dd HEAD@{3}: commit (initial): lorem
正如你在清单 3-11 中看到的,所有的HEAD@{n}引用都被更新了。以下是他们指出的情况:
HEAD@{0}—points to the revision lorem
HEAD@{1}—points to the revision dolor
HEAD@{2}—points to the revision ipsum
HEAD@{3}—points to the revision lorem
接下来,使用以下命令将存储库重置为标题为dolor的版本:
$ git reset --hard HEAD@{1}
在这之后,存储库包含三个文件lorem.txt、ipsum.txt和dolor.txt;并且$ git reflog命令返回如清单 3-12 所示的输出。
***清单 3-12。***git 复位后 git reflog 的输出——硬磁头@{1}
481f34f HEAD@{0}: reset: moving to HEAD@{1}
aae6588 HEAD@{1}: reset: moving to HEAD@{2}
481f34f HEAD@{2}: commit: dolor
84fb524 HEAD@{3}: commit: ipsum
aae6588 HEAD@{4}: commit (initial): lorem
它是如何工作的
Git reflog 是一个特殊的日志,它在存储库中存储关于您的移动的信息。每次创建修订、重置存储库或更改当前修订时,参考日志都会更新。名称HEAD@{0}总是指向当前版本。先前的版本可作为HEAD@{1}获得。两个操作前的当前版本可作为HEAD@{2}获得,等等。因此,即使你不知道他们的名字,你也可以参考以前的修订。
3-9.在现有项目中创建新的存储库
问题
您正在处理一个已经包含大量文件和目录的项目。您想开始在那个项目中使用 git。
解决办法
输入包含您要跟踪的一些文件的目录:
$ cd my/important/project
初始化新的存储库:
$ git init
并创建包含所有文件的修订版:
$ git add -A
$ git commit -m "Initial commit"
当然,您可以使用别名:
$ git snapshot Initial commit
存储库包含存储所有文件的当前状态的单个修订。然后,您可以继续工作,用$ git snapshot或$ git add和$ git commit命令保存所有修改。
它是如何工作的
Git 的 init 命令可以在任何不包含.git子目录的目录中执行。您可以在已经包含由许多文件和子目录组成的项目的目录中运行$ git init。在存储库初始化之后,您可以用两个命令$ git add –A和$ git commit –m "Initial commit"导入所有文件。你也可以使用$ git snapshot Initial commit。
3-10.丢失未提交的更改
问题
如果您忘记提交更改并重置工作目录,您希望检查您的修改会发生什么情况。
解决办法
从配方 3-1 克隆存储库:
$ cd git-recipes
$ git clone 03-01 03-10
$ cd 03-10
创建一个新文件graham-masterton.txt:
$ vi graham-masterton.txt
键入其内容:
Novels
1975 | The Manitou
1977 | The Djinn
1979 | Revenge of the Manitou
保存文件并关闭编辑器。
然后修改文件stephen-king.txt:
$ vi stephen-king.txt
追加两本小说The Stand和The Dead Zone:
Novels
1974 | Carrie
1975 | Salem’s Lot
1977 | The Shining
1978 | The Stand
1979 | The Dead Zone
保存文件并关闭编辑器。
现在,$ git status命令打印出:
M stephen-king.txt
?? graham-masterton.txt
这意味着工作目录中有两个变化:
- 文件
stephen-king.txt已被修改。 - 工作目录包含一个新文件
graham-masterton.txt。
用$ git l别名打印历史。
输出包含我们在配方 3-1 中创建的三个修订:
ffa6 Third revision [Stephen King]
0468 Second revision: [John Grisham]
de36 First commit [Agatha Christie]
假设现在你忘记提交你的工作。当您决定使用以下命令恢复您的第一个修订版时,文件stephen-king.txt和graham-masterton.txt仍然未提交:
$ git reset --hard de36
在该命令之后,工作目录包含两个文件。命令$ ls打印他们的名字:
agatha-christie.txt
graham-masterton.txt
文件stephen-king.txt已经消失。您可以将存储库的状态重置为修订版:
ffa6 Third revision [Stephen King]
可以使用 reflog 来完成:
$ git reset --hard HEAD@{1}
文件stephen-king.txt将被恢复,但是它现在将只包含在配方 3-1 期间键入的三本书。命令:
$ cat stephen-king.txt
印刷品:
Novels
1974 | Carrie
1975 | Salem's Lot
1977 | The Shining
你在配方 3-10 期间键入的两本新小说:
1978 | The Stand
1979 | The Dead Zone
没有存储在数据库中。你还记得吗?您忘记提交更改。在stephen-king.txt中输入的更改已丢失,无法恢复。
显示新文件的内容:
$ cat graham-masterton.txt
通过$ git reset操作,新文件保持不变。
它是如何工作的
这个配方的目的是警告您未提交的更改可能会丢失。请记住,该命令:
$ git reset --hard [REVISION]
只能在干净的目录中安全使用。签出命令:
$ git checkout [REVISION]
是内部限制的。只有在操作不会导致数据丢失的情况下,才能使用它。每当有丢失未提交的更改的风险时,系统将会警告您,并且操作将会中止。这将在配方 5-6 和 5-7 中详细讨论。
3-11.创建 git 简单提交别名
问题
您已经注意到,在学习和实践 git 时,经常需要创建一系列的修订。通常文件的内容并不重要,可以忽略不计。您希望简化使用别名simple-commit创建这种类型的提交的任务。被呼叫时:
$ git simple-commit lorem ipsum dolor
别名应创建如图 3-9 所示的三个版本。每个参数都应该被解释为请求创建一个新的版本来存储一个新的文件。电话:
$ git simple-commit abc
应该创建一个带有注释abc的修订。修订版应包括一个包含文本abc的新文件abc.txt。
解决办法
打开您的.gitconfig文件,并在[alias]部分的末尾键入如列表 3-13 所示的别名。
清单 3-13。 别名:git 创建文件和 git 简单提交
[alias]
create-file = "!createFile() {
for name in \"$@\"; do
echo $name>$name.txt;
done;
}; createFile"
simple-commit = "!simpleCommit() {
for name in \"$@\"; do
git create-file \"$name\";
git snapshot $name;
done;
}; simpleCommit"
它是如何工作的
第一个别名创建文件。电话:
$ git create-file yes no
创建两个文件yes.txt和no.txt。第一个文件包含文本yes,第二个文件包含文本no。for循环:
for name in \"$@\"; do
echo $name>$name.txt;
done;
处理传递给脚本的所有参数。每个参数都可以作为$name变量在一次循环中访问。因此呼吁:
$ git create-file yes no
相当于:
echo yes>yes.txt
echo no>no.txt
第二个别名包含处理所有参数的相同循环:
for name in \"$@\"; do
git create-file \"$name\";
git snapshot $name;
done;
每次循环时,我们调用两个别名:
$ git create-file $name
$ git snapshot $name
电话:
$ git simple-commit yes no
相当于:
$ git create-file yes
$ git snapshot yes
$ git create-file no
$ git snapshot no
电话:
$ git simple-commit lorem ipsum dolor
创建如图 3-9 所示的储存库。
提示秘籍 2-10、3-2 和 3-7 中定义的所有别名在你使用 git 的日常工作中都很有用。配方 3-11 中定义的别名只有在学习和实践 git 时才有用。
3-12.松开提交
问题
您希望验证无法通过符号引用访问的修订是否会丢失。您可以按照以下步骤操作:
- 提交更改。
- 用
$ git reset –hard [REVISION]重置历史。一些提交将不再被$ git log命令返回。它们在数据库中保持不变。您可以使用 reflog 或 SHA-1 名称来访问它们。 - 一旦清除参考日志,未被
$ git log打印的修订将变得不可及。这意味着他们仍然在数据库中,但你可以访问他们只有当你知道他们的 SHA-1 名字。 - 执行完
$ git prune命令后,所有可删除的修订都将从数据库中删除。他们迷路了。没有办法让他们回来。
解决办法
创建新的存储库:
$ cd git-recipes
$ git init 03-12
$ cd 03-12
用注释a、b、c创建三个修订:
$ git simple-commit a b c
使用清单 3-14 中所示的命令,你可以在没有别名的情况下达到同样的效果。
清单 3-14。 该命令相当于 git simple-commit a b c
$ echo a>a.txt
$ git add -A
$ git commit -m a
$ echo b>b.txt
$ git add -A
$ git commit -m b
$ echo c>c.txt
$ git add -A
$ git commit -m c
用$ git l打印历史。输出包含三个修订:
5c1e c
4580 b
c4ac a
将存储库的状态重置为第一个版本:
$ git reset --hard c4ac
虽然用$ git l打印的历史现在只包含一个修订版—c4ac a—但其他两个修订版— 5c1ec和4580b—仍然在数据库中可用。您可以使用它们的名称或 reflog 来恢复它们。我们在配方 3-8 中这样做了。
现在,$ git reflog命令打印出以下输出:
c4ac743 HEAD@{0}: reset: moving to c4ac
5c1ee9a HEAD@{1}: commit: c
45800dd HEAD@{2}: commit: b
c4ac743 HEAD@{3}: commit (initial): a
这意味着修订版5c1ee9ac和45800ddb在符号名称HEAD@{1}和HEAD@{2}下可用。我们称这种类型的修订为悬空修订。让我们用以下命令清除 reflog:
$ git reflog expire --all --expire=now
执行此命令后,reflog 变为空。$ git reflog 命令返回空结果。这意味着现在修订版5c1ee9ac和45800ddb只能通过它们的名字获得。没有符号名称导致修改b和c。如果是这种情况,git 可以从数据库中删除修订。这种类型的修订被称为不可达修订。
让我们检查一下,哪些存储在.git/objects数据库中的对象只能通过 SHA-1 名称访问:
$ git prune --dry-run
输出将包含——除其他内容外——两个修订:
45800ddc19fa325296437fdbd7cc7e5654619597 commit
5c1ee9a3f19f854c783fa87003cb1ecc5508971d commit
如果您比较一下$ git l的输出,您将会看到输出中包含了修订名称5c1ec和4580b。换句话说,如果你现在执行命令$ git prune,那么两个修改b和c将最终丢失。就这么办吧。执行命令:
$ git prune
如果您现在尝试使用其名称将存储库重置为修订版c:
$ git reset --hard 5c1e
您将得到以下错误:
fatal: ambiguous argument '5c1e': unknown revision or path not in the working tree.
该修订版不再可用。你刚刚失去了它!永远!
提示你可以用
$ git fsck --unreachable列出数据库中存储的所有不可访问的对象。
它是如何工作的
Git 将所有修订存储在存储库的数据库.git/objects中。该数据库使用 SHA-1 散列来识别修订、文件和所有其他条目。它是一种内容可寻址存储,这意味着密钥是使用存储数据的内容生成的。有时,数据库会被清除,不能通过符号引用访问的对象最终会被删除。为了熟悉这个过程,我们需要更深入地研究存储库的结构。
在前一章中,我们将 git 存储库分为:
- git 目录
.git/ - 数据库
.git/objects - 工作目录
数据库的内容可以进一步分为:
- 通过各种符号引用(如 reflog、分支和标签)可用的对象被分类为可达的。
- 仅通过 SHA-1 名字可用的对象被分类为不可达。
清理数据库的过程会删除所有无法访问的对象。您可以使用$ git prune或$ git gc命令手动完成。但是即使不使用那些命令,git 也会自动完成——这只是时间问题。关于多久清理一次存储库的确切参数可以在配置中设置。
当我们打电话时:
$ git reflog expire --all --expire=now
参考日志已清除。因此,我们删除了所有指向修订版b和c的符号引用。这就是为什么修订版b和c变得遥不可及。接下来调用:
$ git prune
已从数据库中删除这些修订。
结论
方法 3-12 的目的是向你展示,即使是提交的变更也可以从存储库中删除。您可以通过保留符号引用来避免这种情况。为此,最简单的解决方案是使用分支。配方 5-4 也讨论了丢失已提交变更的问题。
摘要
这一章向前迈出了非常重要的一步。现在,您可以在日常工作中使用 git,而没有任何丢失数据的风险。git 最简单的工作流程是:
-
修改您的文件
-
Save the snapshot of the working directory with two commands:
$ git add -A $ git commit -m "...".该操作由这两个命令实现,如图 3-10 中的所示
图 3-10 。使用 git commit–m 命令
感谢:
$ git reset --hard [REVISION]
$ git checkout [REVISION]
您知道如何检索以前的快照之一。使用$ git checkout或$ git reset您可以访问存储在您的存储库中的每个修订。记住,当使用$ git reset时,参考日志将帮助你返回到最新版本。
从现在开始,您应该记住两种新的重要的描述存储库的方法。存储库可以是:
- 干净还是脏
- 在分离的头部或正常状态下
两个特征都由$ git status –sb命令显示。
当工作目录的内容与当前版本中保存的快照相同时,存储库是干净的。
当工作目录的内容包含未提交的更改时,存储库是脏的。
第二个特征,正常状态和分离的磁头状态,是在.git/HEAD文件的基础上完成的。我们将在关于分支的章节中回到这一点。目前你知道如何检查状态。当执行以下命令时,存储库处于脱离头状态:
$ git status -sb
开始于:
## HEAD (no branch)
否则储存库处于正常状态。
本章中出现的另一个重要特征将数据库的内容分为:
- 可达对象:通过符号引用可获得的对象
- 无法访问的对象:只能通过 SHA-1 名字访问的对象
请记住,在自动垃圾收集过程中,无法访问的对象可以从存储库中删除。这就是为什么你不能在分离的头部状态下工作。当您在分离的 HEAD 状态下工作时,您会创建无法访问的修订,除非您手动创建符号引用(分支或标记)。