Git-秘籍-六-

127 阅读31分钟

Git 秘籍(六)

原文:Git Recipes

协议:CC BY-NC-SA 4.0

十二、使用 Github

在这一章中,我将讨论使用 Github 来托管存储库。目前这是一个流行的开源项目托管平台。

我们首先创建一个 Github 帐户并配置 SSH 密钥。完成后,您将学会如何:

  • 从公共 Github 库克隆
  • 克隆并推送至您的存储库

稍后,我将向您展示如何从头开始一个新的 Github 托管的项目,以及如何导入一个现有的项目。然后,我们将继续进行拉请求。为了同时扮演贡献者和管理员的角色,我们将使用两个 Github 帐户:您的个人帐户和组织帐户。通过这种方式,你可以将你的组织拥有的项目转移到你的个人账户中。

12-1.创建 Github 帐户

问题

您想使用Github.com有两个原因:

  • 托管您自己的 git 存储库
  • 为一些开源项目做贡献

除非您是注册的Github.com用户,否则您不能这样做。因此,您的首要任务是创建一个新的 Github 帐户。

解决办法

启动网络浏览器并访问Github.com 。点击链接“注册 GitHub”。填写登记表并提交。当您使用新创建帐户完成登录时。

如果你想为你的 Github 帐户使用头像,请访问http://gravatar.com网站。在Gravatar.com中创建一个账号,上传你的头像,并与你用于 Github 账号的邮箱关联。

当您在Gravatar.com上完成头像配置后,请转到Github.com,注销,然后再次登录您的帐户。你的帐户现在应该使用新的头像。

它是如何工作的

注册过程很简单,应该很容易使用。这个配方的重点是强调我们在配方 2-1 中提到的事实:Github 不允许使用 SSH 协议,除非你已经配置了自己的 SSH 密钥。命令:

$ git clone git@github.com:jquery/jquery.git

失败,产生以下消息:

Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

产生此错误的原因是:

  • 如果您没有 Github 帐户。
  • 如果您的帐户没有配置为使用 SSH 密钥。

上面$ git clone 命令中的 URL:

git@github.com:jquery/jquery.git

使用 SSH 协议。你可以在表 11-2 中找到 git 使用的协议的完整参考。

12-2.用 SSH 密钥配置 Github 帐户

问题

您希望以 git 允许您使用 SSH URLs 克隆、获取和推送的方式配置 Github 帐户。

解决办法

要完成这个秘籍,你需要一个 SSH 密钥对。检查您的∼/.ssh/目录是否包含它们。打开一个 bash 窗口,列出。ssh 目录:

$ ls ∼/.ssh

如果这个目录不包含id_rsaid_rsa.pub文件,那么您必须生成它们。您需要的命令是:

$ ssh-keygen -t rsa -C your.email@example.net

该命令将询问您两个问题,这两个问题已在 RSA 密钥部分的方法 11-5 中解释过。第一个问题用键设置两个文件的名称;第二个设置使用密码来保护您的密钥。如果您按下输入,文件将被存储在默认位置,密码将不会被使用。

id_rsaid_rsa.pub文件准备好后,启动你的网络浏览器并访问 Github.com。点击标题为“注册 GitHub 完成后,使用您新创建的帐户登录。

要使用基于密钥的认证,您必须将您的公钥上传到您的帐户。将存储在∼/.ssh/id_rsa.pub中的公钥内容复制到剪贴板上。如果您在 Windows 上工作,可以使用以下命令:

$ clip < ∼/.ssh/id_rsa.pub

在其他系统上工作的读者可以用他们喜欢的文本编辑器打开文件,并使用编辑/复制命令。

进入你的 Github 账户设置,打开 SSH 密钥菜单选项,然后按下添加 SSH 密钥按钮。当您看到标题为Add a SSH Key的表单时,将剪贴板的内容粘贴到表单中。然后按下添加键按钮。添加 SSH 密钥的过程如图 12-1 所示。按照标记为 A、B、C、D 和 e 的箭头进行操作。完成后,上传的密钥将列在 SSH 密钥下,如图 12-2 所示。

9781430261032_Fig12-01.jpg

图 12-1 。在 Github 帐户上添加新的 SSH 密钥

9781430261032_Fig12-02.jpg

图 12-2 。成功上传的密钥列在 SSH 密钥下

配置完成。您可以通过使用 SSH 协议克隆任意的公共存储库来验证这一点。如果命令:

$ git clone git@github.com:jquery/jquery.git

成功,那么您的密钥是正确的。您可以使用 SSH 协议来访问 Github 上的存储库。

它是如何工作的

SSH 协议不允许匿名访问。要使用它,您需要一个服务器上的帐户。Github 对所有用户使用一个名为 git 的帐户。这就是为什么所有克隆命令都包含 git@github,如:

$ git clone git@github.com:jquery/jquery.git

上面的命令意味着您正在主机 github.com 上访问一个 git 帐户。只有在基于密钥的认证成功的情况下,在 github.com 上工作的 SSH 守护进程才会授予对 git 帐户的访问权限——如果您的公共 SSH 密钥是为您的 git@github.com 帐户配置的,就会发生这种情况。否则,您将无法访问任何资源。这种托管解决方案在配方 11-5 和 11-10 中进行了解释。在图 12-1 和图 12-2 中呈现的 web 界面只是简化了管理你的公共 SSH 密钥的任务。

12-3.为新项目创建 Github 托管的存储库

问题

你想开始一个新项目,并把它放在 Github 上。

解决办法

启动您的网络浏览器并登录到 Github.com。然后点击创建新的回购按钮。按钮创建新的回购在 Github 网页右上角显示的菜单上(参见图 12-3 中的 A)。您将在图 12-1 和图 12-2 中找到相同的按钮。

9781430261032_Fig12-03.jpg

图 12-3 。在Github.com创建新的存储库

当您按下创建一个新的回购按钮时,您将看到图 12-3 中显示的表单。在储存库名称编辑框中输入 12-03,然后按下创建储存库按钮(参见图 12-3 中的 B 和 C)。

此后,您将看到图 12-4 所示的页面,其中显示了该存储库的 URL 以及您可以用来开始工作的两个程序。假设您的 Github 帐户名为 john-doe,那么您的存储库的 URL 将如下所示:

git@github.com:john-doe/12-03.gi

9781430261032_Fig12-04.jpg

图 12-4 。新创建存储库的网页

你会在图 12-4 所示的编辑框中找到。

Github 的工作目前已经完成。打开 bash 命令行并执行以下命令:

# the commands should be executed in git-recipes directory
$ cd git-recipes
$ git clone git@github.com:john-doe/12-03.git
$ cd 12-03
$ git simple-commit a b c
$ git push -u origin master

现在回到 Github.com,跟随图 12-5 中的链接,你会看到存储在库中的文件列表。该列表将包括三个文件:a.txtb.txtc.txt,如图 12-5 中的 B 箭头所示。

9781430261032_Fig12-05.jpg

图 12-5 。$ git push -u origin 主命令将主分支从您的驱动器发送到您的由 Github 托管的存储库

它是如何工作的

要在 Github 上创建一个新的存储库,你应该使用 web 界面。创建新存储库后,您可以克隆它。使用本地克隆创建新的提交,当您想要在 Github 上发送更新的存储库时,使用$ git push命令。使用图 12-5 中 C 箭头所指的按钮,您可以显示储存在库中的修订列表。列表如图图 12-6 所示。

9781430261032_Fig12-06.jpg

图 12-6 。Github 提供的修订列表

正如你在配方 8-1 中所记得的,每个版本都存储了两个特殊的属性来分配作者。分别是AuthorCommitAuthor是创造代码的人的名字。Commit是版本创建者的名字。对于图 12-6 中出现的修改,AuthorCommit都设置为同一个人,Włodzimierz Gajda和 email gajdaw@gajdaw.pl。Github 用这封邮件来猜测账户的名字。因此,我的提交被标注为由gajdaw创作。因为AuthorCommit数据相同,所以作者简化为简单的gajdaw。这意味着gajdaw创建了代码和提交。

如果提交包含不同名称的Author和不同名称的Commit会发生什么?Github 显示这些信息,如图图 12-7 所示。在图 12-7 中展示的修订版是由 John Doe 编写的,并由 gajdaw 重新修改。Github 显示这些消息:

John Doe authored...
Gajdaw committed...

9781430261032_Fig12-07.jpg

图 12-7 。对于不同作者和提交数据的修订,Github 会同时显示两者

image 提示如何创建如图图 12-7 所示的版本?您可以使用配方 8-1 中描述的$ git commit --amend。您可以以类似的方式使用$ git cherry-pick$ git am$ git rebase命令。

12-4.为现有项目创建 Github 托管的存储库

问题

你已经为你的项目工作了一段时间,现在你想在 Github 上发布它。

解决办法

使用以下命令创建一个项目:

$ cd git-recipes

$ git init 12-04

$ cd 12-04

$ git simple-commit a b c d

启动您的 web 浏览器,登录到 Github.com,并使用图 12-3 所示的过程创建一个名为 12-04 的新的空存储库。

当你到达图 12-4 所示的页面时,你会看到上传的说明:如何将一个现有的项目上传到这个新创建的存储库中。转到 bash 命令行,执行 Github 提示的两个命令:

# the commands should be executed in git-recipes/12-04 directory
$ git remote add origin git@github.com:your-github-username/12-04.git
$ git push -u origin master

当执行这些命令时,您的 Github 存储库应该包含来自您的项目的所有修订:abcd

它是如何工作的

如果您想使用 Github 发布一个现有的项目,您必须创建一个新的空存储库,然后执行两个命令:

$ git remote add origin git@github.com:your-github-username/12-04.git
$ git push -u origin master

12-5.在 Github 上创建组织账户

问题

Github 接口包含一个叫做 pull 请求的特殊操作。它简化了开源项目的工作流程。如果您想练习如何处理拉取请求:

  • 一个发送他的贡献的开发者

  • 接受贡献代码的项目所有者需要两个 Github 角色。在这个秘籍中,你要在 Github 上创建你自己的组织。这将让你有机会在两种不同的性格下工作:

  • 作为普通用户

  • 作为组织的所有者

在接下来的课程中,我们将使用这两个帐户来练习拉取请求。

解决办法

要创建您自己的组织,请访问您的 Github 帐户并进入您帐户的设置,然后进入组织并按下创建新组织按钮。按照图 12-8 中的按钮操作。

9781430261032_Fig12-08.jpg

图 12-8 。创建新组织

新建组织按钮将打开如图图 12-9 所示的对话框。输入您的组织名称和电子邮件地址。我在 Github 上的用户名是 gajdaw,我使用 gajdaw-learning-git 作为我的组织的名称,但是你可以选择任何你喜欢的名称。然后点击页面底部的创建组织

9781430261032_Fig12-09.jpg

图 12-9 。新组织的属性

创建组织后,您可以使用以下 URL 访问其页面:

https://github.com/gajdaw-learning-git

不要使用 gajdaw-learning-git,而是使用您在图 12-9 所示的对话框中输入的名称。我的组织 gajdaw-learning-git 的主页如图图 12-10 所示。

9781430261032_Fig12-10.jpg

图 12-10 。gajdaw-learning-git 组织的主页位于https://github.com/gajdaw-learning-git

它是如何工作的

Github 界面允许你创建组织。因为每个 Github 都可以拥有自己的库,所以您可以使用您的组织来模拟 pull 请求的工作。

注意 Github 数据库许可证严格禁止一个人创建多个帐户。因此,您不能创建两个不同的用户帐户来处理拉请求。

12-6.创建由组织托管的新项目

问题

您想要启动一个由您的组织托管的新项目。

解决办法

访问您的 Github 帐户并:

  1. 按下创建新的回购按钮
  2. 所有者下拉列表中选择您的组织名称
  3. 将新存储库的名称填写为 12-06
  4. 点击页面下部的创建存储库按钮

所有步骤如图 12-11 所示。

9781430261032_Fig12-11.jpg

图 12-11 。创建由您的组织拥有的新存储库

现在打开一个 bash 命令行并执行以下命令:

$ cd git-recipes

$ git clone git@github.com:your-github-organization/12-06.git

$ cd 12-06

$ git config --local user.name admin

$ git config --local user.email admin@example.net

$ git simple-commit a b c

$ git push -u origin master

您必须将您的名称-github-organization 替换为您在图 12-9 中使用的名称。当您运行上述命令时,您在 Github 上托管的存储库现在应该包含由 admin 编写的修订版 a、b、c。

它是如何工作的

对您的组织所拥有的存储库的处理与对您所拥有的存储库的处理是相同的。秘籍 12-3 和 12-6 的唯一区别是,在后者中,您将所有者从您的个人帐户更改为您的组织。

在存储于git-recipes/12-06的存储库中,您将使用管理员身份工作。

12-7.发送拉取请求

问题

Github 主持了一个名为 12-06 的项目。该项目可从以下 URL 获得:

https://github.com/your-github-organization/12-06

您希望为这个项目做出贡献,发送一个包含三个提交的 pull 请求。

解决办法

启动 web 浏览器,登录到您的 Github 帐户,然后转到以下 URL:

https://github.com/your-github-organization/12-06

您将看到图 12-12 中的所示的网页。确保您正在访问由 a 标识的项目 your-github-organization/12-06。

9781430261032_Fig12-12.jpg

图 12-12 。您希望贡献的 your-organization/12-06 知识库的主页

使用标识为 B 的 Fork 按钮来创建您个人的库副本。当您按下按钮时,将会询问您“我们应该在哪里分叉这个存储库?”选择您的个人帐户(不是您的组织)来回答问题。这将重定向到如图图 12-13 所示的网页。您应该注意到,这一次存储库在您的用户名下可用,如下所示:

your-github-username/12-06

9781430261032_Fig12-13.jpg

图 12-13 。您的组织拥有的原始存储库的私有分支

在图 12-13 中显示了可以验证存储库名称的地方。

在这个菜谱中,我们将使用两个存储库。它们的网址是:

  • 原始存储库:git@github.com:your-organization/12-06.git
  • 你的叉子:git@github.com:your-github-username/12-06.git

现在是时候在你的硬盘上创建一个克隆了。打开 bash 命令行,键入以下命令:

$ cd git-recipes

$ mkdir 12-07

$ cd 12-07

$ git clone git@github.com:your-organization/12-06.git.

好好看看上面的命令。这应该是原始存储库的 URL。

现在添加我使用的远程 URL 别名,并指向您的分支:

$ git remote add my git@github.com:your-github-username/12-06.git

保存在 git-recipes/12-07 中的本地存储库包含两个远程别名。命令$ git remote -v打印以下结果:

$ git remote -v
my      git@github.com:your-github-account/12-06.git (fetch)
my      git@github.com: your-github-account/12-06.git (push)
origin  git@github.com:your-organization/12-06.git (fetch)
origin  git@github.com: your-organization/12-06.git (push)

使用以下两个命令检查您的身份:

$ git config user.name
$ git config user.email

上述命令的输出应该包括您的真实姓名和真实电子邮件地址。

您已经准备好为原始存储库做贡献了。为此,创建一个新分支:

$ git checkout -b numbers

创建三个版本一、二、三,使用:

$ git simple-commit one two three

将名为numbers的分支推到 Github 上的 fork,方法是:

$ git push -u my HEAD

转到 web 浏览器,检查您的 fork 是否包含新推送的分支。使用图 12-14 中所示的按钮。

9781430261032_Fig12-14.jpg

图 12-14 。你推的分支在你的叉子里有。您可以使用箭头所指的按钮来查看它

现在,您已经准备好将您的提交发送到原始存储库。按比较&拉动要求如图图 12-15 所示。

9781430261032_Fig12-15.jpg

图 12-15 。按钮比较&拉动请求

图 12-15 中的按钮将打开图 12-16 中所示的对话框。字母 A 显示了关于以下内容的准确信息:

  • 将接收拉取请求的分支

    your-organization:master
    
  • 将被推送的分支

    your-github-username:numbers
    

9781430261032_Fig12-16.jpg

图 12-16 。发送拉请求的对话框

图 12-16 中的字母 B 包含您需要键入的标题。我用 foo bar 作为标题。C 指向您的拉取请求背后的目的的完整描述。d 列出将被推送的提交。要继续请求拉动,点击发送拉动请求

点击发送拉取请求后,你将被重定向到图 12-17 所示的网页。这是您派生的原始存储库。

9781430261032_Fig12-17.jpg

图 12-17 。提取请求已成功发送,并且在贵组织拥有的原始存储库中可用

你的工作结束了。提取请求已发送到原始存储库。这取决于该存储库的所有者是否接受您的“拉”请求。

它是如何工作的

拉请求的工作包括三个存储库,如下所示:

  • Github 上托管的原始存储库
  • 你的 fork 托管在 github
  • 存储在机器上的本地存储库

您负责其中的两个:您的 fork 和您的本地存储库。第一个是在图 12-8 所示的 Githhub 界面中点击 Fork 创建的。本地存储库是作为原始存储库的克隆创建的。三个仓库之间的关系如图图 12-18 所示。

9781430261032_Fig12-18.jpg

图 12-18 。处理拉请求所需的三个存储库之间的关系

使用拉取请求时,工作流组织如下:

  • 您只能在您的本地存储库中提交、合并和重设基础。
  • 您将更改从本地存储库推送到您的分支。
  • 您将 pull 请求从 fork 发送到原始存储库。
  • 您从原始存储库获取最新的更新到您的本地存储库。

命令流程如图 12-19 中的所示。

9781430261032_Fig12-19.jpg

图 12-19 。处理拉请求时的命令流

因为您的本地存储库是一个克隆的原始存储库,origin别名指向组织拥有的存储库。您可以通过以下方式获取其他开发人员所做的最新更新:

$ git fetch origin

如你所知,fetch 不会移动你的本地跟踪分支。要合并本地主分支中的最新修订,您必须执行 merge 或 rebase,例如:

$ git rebase origin/master master

为了使推送到 fork 更容易,您需要为这个存储库设置一个别名。命令:

$ git remote add my git@github.com:your-github-username/writers.git

定义一个名为my的别名。因此,如果您想使用 fork,可以使用以下命令:

$ git push my branch-name
$ git push my HEAD

第一个命令推送具有给定名称的分支,第二个命令推送当前分支。

当您的分支和您的贡献在分叉的存储库中可用时,您可以发送一个 pull 请求。

我更喜欢通过克隆由 organization 托管的原件来创建存储在我的机器上的本地存储库。然后,别名 original 用于获取其他人最近做出的贡献,而我的别名用于将我的新贡献推送到分叉存储库中。但是您也可以使用其他设置。您可以克隆您的 fork,并为 organization 托管的原始存储库定义一个名为 upstream 的新远程。然后,您将使用 origin alias 将您的贡献推送到 fork,并使用 upstream alias 下载最新的贡献。除了重命名的遥控器,这两种解决方案实际上没有区别。

12-8.返工您的拉动式请求

问题

您参与了一个发送拉请求的开源项目。但是你的贡献没有被接受。你被要求做一些改进。您希望在您的拉请求中添加两个新的提交。

解决办法

转到您的本地存储库,您在其中创建了作为拉请求发送的修订。它位于 git-recipes/12-07 目录中:

$ cd git-recipes/12-07

我假设您发送了一个 pull 请求,将本地 numbers 分支的修订合并到原始存储库中的主分支。为了获得清晰的线性历史记录,您应该使用远程主分支中的最新版本更新您的本地分支。执行以下命令:

$ git fetch origin

$ git rebase origin/master numbers

这些命令会将你的修改移动到origin/master分支的顶部。现在,您可以向 pull 请求添加一些提交。你目前的分行在重定基数后是数字。在其中创建两个新版本:

$ git simple-commit red green

把它推到你的叉子上:

$ git push -f my HEAD

您的拉动请求现在应该包含红色和绿色两个新版本。你可以用下面的方法来检查。转到原始库,按照图 12-20 所示的拉取请求按钮。

9781430261032_Fig12-20.jpg

图 12-20 。“拉请求”按钮提供了所有拉请求的列表

现在从列表中检查您的拉取请求。你会在窗口的下部看到你的两个修改,如图图 12-21 所示。

9781430261032_Fig12-21.jpg

图 12-21 。推送到分叉存储库中适当分支的新修订会自动添加到拉请求中

它是如何工作的

分叉存储库中的分支可用于向拉请求添加更多的提交。只要拉取请求未被接受,您就可以推送到该分支。

原始存储库中的主分支可能会向前移动。如果是这种情况,您应该使用以下内容更新您的本地存储库:

$ git fetch origin

$ git rebase origin/master numbers

当新的提交准备好时,您可以用一个命令将它们附加到现有的拉请求中:

$ git push -f my numbers

如果在更新阶段你的修改被重新设定了基数,标志-f是必要的。如果原始主分支中没有新的修订,那么您可以使用不带-f标志的命令:

$ git push my numbers

请记住,您当地的分支机构,以及您 fork 中的分支机构,在被接受之前,都被视为您的私人工作。这意味着您可以使用第八章中描述的任意方法来调整历史记录。您可以添加新的提交,可以对提交进行重新排序,可以删除提交,最后,您可以将所有提交压缩成一个提交。

请记住,每当您想要为 Github 上的项目做贡献时,请使用分支。不要为此在你的总分行工作。如果您在单独的分支中准备您的拉取请求,这不会使您的工作变得复杂。当拉取请求被拒绝时,您只需删除该分支。否则,您需要使用$ git reset --hard HEAD∼n来调整主分支,以删除被拒绝的修订。

12-9.接受拉取请求

问题

你拥有一个流行的知识库。开发人员通常会为您的项目做出贡献。您希望接受刚刚收到的拉请求。

解决办法

在这个秘籍中,你将只在 Github 提供的网络界面中工作。转到您的组织,然后选择存储库 12-06 并打开可用的“拉”请求列表。接下来,打开包含有关拉取请求的详细信息的页面。您可以按照图 12-22 中的所示的链接进行操作。

9781430261032_Fig12-22.jpg

图 12-22 。由您的组织托管的存储库中所有提取请求的列表

拉动请求的细节如图 12-23 中的所示。您可以分析所有贡献的代码。当你确定代码是正确的,你可以点击图 12-23 所示的按钮进行合并。

9781430261032_Fig12-23.jpg

图 12-23 。用于合并拉取请求的按钮

如图 12-24 所示,当拉动请求被合并时,它将包含在项目历史中。

9781430261032_Fig12-24.jpg

图 12-24 。合并的拉取请求在项目历史记录中可用

它是如何工作的

发送到存储库的所有拉请求都列在图 12-22 所示的页面上。你可以彻底检查他们每一个人。特别是,Github 允许您:

  • 列出包含在拉请求中的提交
  • 列出拉请求修改的文件
  • 要列出修改的行
  • 与其他开发人员讨论拉取请求

当您确定拉动请求正确时,您可以使用图 12-23 中的所示的合并拉动请求按钮来接受它。

摘要

在这一章中,我向你提供了使用 Github 作为项目托管平台的基本信息,以及为他人所有的项目做贡献的程序。

如果您想使用 Github 托管一个开源项目,请遵循秘籍 12-3、12-4 和 12-6。每当你收到一个拉动请求,你可以按照秘籍 12-9 接受它。

也许你想为一个开源项目做贡献?你可以用秘籍 12-7 和 12-8 中解释的方法来做这件事。

如果你主持一个由你信任的开发团队开发的项目,你可以避免你和你的朋友使用拉请求。设置适当的权限,你可以允许其他 Github 用户直接推送到原始存储库。在图 12-25 中所示的团队管理链接下有一个对话框。

9781430261032_Fig12-25.jpg

图 12-25 。“团队管理”链接会将您带到可以为其他开发人员分配权限的页面

如果您需要更多的参数来尝试 Github,请考虑它包含一个嵌入式问题跟踪器。所有参与该项目的贡献者都可以用它来讨论提交的代码。所有这些特性使 Github 成为一个真正伟大的代码共享平台。

十三、更多秘籍

在这最后一章,我将讨论一些尚未涉及的细节,这些细节迟早会成为你不可或缺的。您将了解到:

  • 如何使用命令$ git diff比较不同版本的文件?
  • 如何克服关于行尾的问题?
  • 配置忽略文件的三种不同方法
  • 使用标签
  • 命令$ git archive生成包含项目的压缩包

13-1.使用$ git diff 命令

问题

您想学习如何使用$ git diff命令来分析同一文件的两个版本之间的差异。

解决办法

创建新的存储库:

$ cd git-recipes

$ mkdir 13-01

$ cd 13-01

$ git init

然后创建名为numbers.txt的文件,其内容如清单 13-1 所示。

清单 13-1。 第一版文件编号. txt

one
two
three
four
five
six
seven
eight
nine
ten

使用以下命令提交文件:

$ git add -A
$ git commit -m "Numbers: en"

现在,存储库是干净的,三个快照——第一个存储在HEAD中,第二个存储在暂存区中,第三个存储在工作目录中——都包含相同版本的numbers.txt文件。

接下来,更改文件的内容。将四个单词fourfivesixseven替换为包含单词foobar的两行。您应该获得的文件如清单 13-2 中的所示。

清单 13-2。 第二版文件编号. txt

one
two
three
foo
bar
eight
nine
ten

命令$ git status -sb现在将文件打印为:

_M numbers.txt

文件已修改,但尚未暂存。

命令$ git diff现在将产生如清单 13-3 所示的输出。当不带任何参数执行时,命令$ git diff比较工作目录和暂存区。

清单 13-3。$ git diff 命令的输出

index f5ef170..a769e64 100644
--- a/numbers.txt
+++ b/numbers.txt
@@ -1,10 +1,8 @@
 one
 two
 three
-four
-five
-six
-seven
+foo
+bar
 eight
 nine
 ten

将文件numbers.txt暂存为:

$ git add -A

该命令之后,文件处于M_状态。命令$ git status -sb将会显示:

M_ number.txt

现在,命令$ git diff打印空的结果。这意味着临时区域中的文件与工作目录中的文件相同。如果您想比较暂存区中的文件和存储在HEAD中的文件,您可以使用附加参数--staged:

$ git diff --staged

上述命令比较存储在HEAD中的文件和暂存区中的文件。结果将与清单 13-3 中的完全相同。

image 提示命令$ git diff将工作目录与暂存区进行比较。命令$ git diff --staged将暂存区与 HEAD 指向的版本中存储的版本进行比较。

现在,使用以下命令提交阶段性更改:

$ git commit -m "Numbers: foo bar"

命令$ git status -sb证明您的存储库是干净的。所有三个快照,HEAD、staging area 和工作目录,包含完全相同版本的文件numbers.txt。因此,这两个命令:

$ git diff
$ git diff --staged

打印空结果。

使用以下命令,通过比较下一个到最后一个版本HEAD∼和最后一个版本HEAD来完成配方:

$ git diff --unified=1 HEAD∼ HEAD

上述命令的输出如清单 13-4 所示。

清单 13-4。$ git diff-unified = 1 头的输出∾头命令

diff --git a/numbers.txt b/numbers.txt
index f5ef170..a769e64 100644
--- a/numbers.txt
+++ b/numbers.txt
@@ -3,6 +3,4 @@ two
 three
-four
-five
-six
-seven
+foo
+bar
 Eight

附加参数--unified=1更改了更改内容前后的行数。

它是如何工作的

命令$ git diff使用 GNU diffutils 工具定义的格式,可从以下网址获得:

http://www.gnu.org/software/diffutils/

$ git diff命令产生符合以下格式的输出:

--- a/some-file-name
+++ b/some-file-name
@@ -a,b +c,d @@
xxx
+yyy
-zzz
qqq

上面的描述明确地定义了文件的两个版本:应用更改之前的版本和应用更改之后的版本。使用上面的输出,我们可以构建两种状态的文件:更改前和更改后。

前两行告诉您输出描述了名为some-file-name的文件的变化。通过删除以+开头的行并写入以-开头的行(不带前导破折号),可以检索到更改前的版本。上面的输出描述了包含更改之前的文件:

xxx
zzz
qqq

更改后的版本可以通过删除以破折号开头的行并写入以+开头且不带前导加号的行来检索。更改后的文件如下所示:

xxx
yyy
qqq

专线:

@@ -a,b +c,d @@

定义了两个线条范围:a,bc,d。第一个范围a,b描述了文件的第一个版本。第二个范围,c,d描述了文件的第二个版本。

范围a,b表示该版本在更改前:

xxx
zzz
qqq

从第a行开始,包含第b行。

第二个范围c,d表示修改后的这个版本:

xxx
yyy
qqq

从第c行开始,延续到第d行。

$ git diff命令允许您更改输出中使用的行数。命令:

$ git diff --unified=4

以这样一种方式改变$ git diff的行为,即每一个改变都被四条未修改的线包围,如:

--- a/some-file-name
+++ b/some-file-name
@@ -a,b +c,d @@
xxx
xxx
xxx
xxx
+yyy
-zzz
qqq
qqq
qqq
qqq

清单 13-4 中的显示的输出是用--unified=1生成的,因此描述被单线包围(它们包含单词threeeight):

@@ -3,6 +3,4 @@ two
 three        <- the first surrounding line
-four
-five
-six
-seven
+foo
+bar
 eight        <- the last surrounding line

感谢3,6,我们知道文件的第一个版本从第3行开始,由6行组成:

three
four
five
six
seven
eight

3,4告诉我们文件的第二个版本从第3行开始,由4行组成:

three
foo
bar
eight

为了产生如清单 13-4 所示的输出,我们传递了两个标识符HEAD∼HEAD来比较不同的修订。同样,您可以比较不同的分支:

$ git diff master dev

以及存储在不同分支中的文件:

$ git diff master dev -- some-file

默认情况下,$ git diff比较行。您还可以更改其行为来搜索已更改的单词。这可以通过以下方式实现:

$ git diff --word-diff

如果你想找到给定文件some-file被修改的版本,你可以使用两个:

$ git diff --name-only master dev -- some-file

$ git log --name-only master dev -- some-file

image 提示命令$ git diff有一个非常有用的参数--check,可以用来验证提交没有引入只影响白色字符的更改。命令$ git diff --check 报告处理白色字符的问题。

13-2.提交文件而不进行行尾转换

问题

您想要启动一个新的存储库,其中包含具有不同行尾类型的文本文件。有些使用类似 Linux 的由单个LF字符组成的行尾,有些使用类似 Windows 的由两个字符CRLF组成的行尾。您的存储库甚至包含使用两种类型的文件:混合在一个文件中的LFCRLF。您希望提交所有文件,而不对行尾字符进行任何转换。

image 提示首先,您可能会认为同时使用了LFCRLF的文件已损坏。但无论如何你都可能需要它们。当我在一个库上工作来处理由外部工具产生的文本文件时,我发现它们作为测试的静态附件非常有用。原来,我使用的应用生成了损坏的文件,不仅包含LFCRLF,还包含CR作为行尾。三个都混在一个文件里!

解决办法

初始化新的存储库:

$ cd git-recipes

$ mkdir 13-02

$ cd 13-02

$ git init

创建存储在不同目录中的三个文件:

$ mkdir linux
$ mkdir mixed
$ mkdir windows

$ printf "linux \n a \n b \n c \n d" > linux/abcd.txt
$ printf "mixed \n a \r\n b \n c \r\n d" > mixed/abcd.txt
$ printf "windows \r\n a \r\n b \r\n c \r\n d" > windows/abcd.txt

第一个名为linux/abcd.txt的使用了LF行尾(它们通常在字符串中被编码为\n)。名为mixed/abcd.txt的第二个文件包含了LFCRLF行尾。最后一个名为windows/abcd.txt的文件使用了CRLF行尾。这些在嵌入字符串时,写成\r\n。您可以使用以下命令验证行尾:

$ hexdump -c linux/abcd.txt

$ hexdump -c mixed/abcd.txt

$ hexdump -c windows/abcd.txt

要提交文件而不转换新行,请关闭core.autocrlf设置:

$ git config --local core.autocrlf false

提交文件:

$ git add -A
$ git commit -m "Three files: linux, windows and mixed line endings"

现在,存储在数据库.git/objects中的最近提交包含以下行尾:

  • linux/abcd.txt用途LF
  • mixed/abcd.txt同时使用LFCRLF
  • windows/abcd.txt用途CRLF

工作目录和临时区域中的文件完全相同。

它是如何工作的

Git 配置包含一个选项core.autocrlf。该选项控制 git 处理行尾转换的方式。它可以取三个不同的值:trueinputfalse。因为行尾的转换可以在签出或提交文件时执行,所以我们必须分析这两种情况下每个值的含义。

第一个值true,影响签出和签入。在结帐过程中,git 将LF字符转换成CRLF。提交时,执行反向转换:CRLF行尾转换为LF

第二个值input,在检入操作期间开启从CRLFLF的转换。使用此设置执行签出时,没有转换。

最后一个值false,关闭所有转换。存储在对象数据库中的文件与工作目录中的文件具有相同的行尾。

core.autocrlf的三个值的含义在表 13-1 中进行了总结。

表 13-1 。core.autocrlf 选项的所有值及其对签出和提交的影响

|

价值

|

检验

|

犯罪

| | --- | --- | --- | | true | LF => CRLF | CRLF => LF | | input | 没有人 | CRLF => LF | | false | 没有人 | 没有人 |

正如你在配方 8-4 和表 8-1 中所记得的,你的存储库由三个快照组成。我们可以将它们表示为HEAD、暂存区和工作目录。在资源库 13-02 中,这三个区域包含表 13-2 中显示的行尾。

表 13-2 。存储库 13-02 中三个快照的行尾

image

所有三个快照都使用完全相同的行尾。

13-3.在不进行行尾转换的情况下签出文件

问题

您的 git 配置包含设置为truecore.autocrlf选项。因此,当您克隆一个存储库时,工作目录中的行尾会被转换成CRLF

您刚刚克隆了存储库,并将core.autocrlf设置为true。您的意图是使工作目录中的文件与数据库中的文件完全相同。因为core.autocrlf被设置为true,你已经创建了一个你认为损坏的克隆体。你想改正你的错误。

你的任务是再次签出所有文件。这一次你要避免任何行尾的转换。您希望工作目录中的行尾与数据库中 HEAD revision 中存储的行尾相匹配。

解决办法

为了理解解决方案,您首先需要创建一个包含损坏文件的存储库。

通过以下方式将core.autocrlf设置为true:

$ git config --global core.autocrlf true

然后从以前的配方中克隆存储库:

$ cd git-recipes

$ git clone 13-02 13-03

core.autocrlf设置为true导致了linux/abcd.txt文件的转换。它现在包含了CRLF行尾。带有混合行尾的文件,即mixed/abcd.txt,未被转换。在core.autocrlf设置为true的克隆后,您得到的行尾在表 13-3 中总结。

表 13-3 。core.autocrlf 设置为 true 的$ git clone 命令之后,存储库 13-03 中三个快照的行尾

image

现在,工作目录包含存储在 HEAD revision 中的不同行尾。因此,您可能认为您的存储库很脏;然而,事实并非如此。如果您使用$ git status命令,您将看到存储库是干净的。这是因为 git 会为您处理行尾的转换。这可能导致另一个问题:如何提交带有CRLF行尾的文件?我们将在秘籍 13-4 中讨论这个问题。

现在,您认为您的工作目录已经损坏。您的意图是让工作目录中的文件具有与 HEAD 中相同的行尾。要再次执行签出操作,这次不对行尾进行任何转换,请按照以下步骤操作:

  1. 删除所有被跟踪的文件:$ git ls-files | xargs rm
  2. 移除暂存区:$ rm .git/index
  3. 关闭新行转换:$ git config --local core.autocrlf false
  4. 重新创建工作目录和暂存区:$ git reset --hard

现在暂存区和工作目录包含的文件与它们在HEAD版本中存储的完全一样。存储库中使用的行尾与表 13-2 中的相同。

它是如何工作的

如果您将选项core.autocrlf设置为true,然后克隆存储库,使用LF行尾的文件将被转换为使用CRLF行尾。因此,紧接着:

$ git clone 13-02 13-03

文件linux/abcd.txt现在包含了CRLF行尾。包含LFCRLF的文件,如mixed/abcd.txt,不会被转换。

要执行在工作目录和临时区域中重新创建所有文件的签出,您必须:

  • 删除所有跟踪的文件
  • 移除临时区域
  • 使用带有--hard选项的$ git reset命令

所有被跟踪文件的列表由$ git ls-files命令返回。如果用xargs将结果列表传递给$ rm命令:

$ git ls-files | xargs rm

所有被跟踪的文件都将从您的工作目录中删除。

暂存区存储在.git/index文件中。您可以用$ rm .git/index命令删除这个文件。

执行上述命令后,暂存区和工作目录不再包含存储在HEAD中的文件。正如您已经知道的,您可以用$ git reset --hard命令重新创建工作目录和暂存区。该命令使用存储在HEAD中的快照重新创建工作目录和暂存区。如果在core.autocrlf设置为false的情况下执行操作,暂存区和工作目录将使用原始行尾(存储在HEAD快照中的行尾)填充文件。

当你完成配方 13-3 时,储存库将包含表 13-2 中显示的行尾。

13-4.在签出和提交更改期间,将行尾转换为工作目录中的 CRLF

问题

您在包含使用不同行尾的文本文件的存储库中工作。你想:

  • 将工作目录中的所有文件转换为使用CRLF行尾
  • 将带有CRLF行尾的文件提交到存储库中

这个版本应该在内部(即在 git 数据库中)使用CRLF编码。如果有人在没有任何新行转换的情况下克隆了这个存储库(例如,将autocrlf设置为false,他们应该使用CRLF获得包含所有文本文件的工作目录。

解决办法

从配方 13-1 克隆存储库:

$ cd git-recipes

$ git clone 13-02 13-04

$ cd 13-04

现在将工作目录中的行尾转换为CRLF:

  1. $ git config --local core.autocrlf truecore.autocrlf选项设置为true
  2. $ git ls-files | xargs rm删除所有被跟踪的文件
  3. $ git reset --hard恢复所有被跟踪的文件

存储在工作目录中的文件linux/abcd.txt现在使用CRLF行尾。您希望将该文件提交到存储库中,以使存储在数据库中的对象使用CRLF行尾。但是命令$ git status -sb打印出工作目录干净的信息。因此你不能用简单的$ git add$ git commit命令提交这个文件。

要提交以CRLF行结尾的linux/abcd.txt文件,您必须更新暂存区。遵循以下程序:

4.用$ git config --local core.autocrlf falsecore.autocrlf选项设置为false

5.用$ rm .git/index移除暂存区

6.用$ git reset重新创建.git/index文件

$ git status -sb检查所有文件的状态。如您所见,文件linux/abcd.txt被列为已修改。命令:

$ git diff

产出:

-linux
- a
- b
- c
+linux ^M
+ a ^M
+ b ^M
+ c ^M

在上面的输出中,字符^M代表CR。我们可以说文件linux/abcd.txt被改变了,每一行都包含了一个新的CR字符。

因为$ git status -sb打印了存储库是脏的信息,所以您可以创建一个新的提交。使用以下命令提交所有已更改的文件,完成配方

$ git add -A
$ git commit -m " Standardization: committing line endings changed to CRLF"

现在,储存在 13-04 储存库数据库中的最新版本包含了表 13-4 中的行尾。

表 13-4 。存储库 13-04 的最终状态中的三个快照中的行结束

image

它是如何工作的

如果您:

  • $ git config --local core.autocrlf true打开行尾转换
  • 移除跟踪的文件
  • 签出跟踪的文件

以前使用LF的文件,如linux/abcd.txt,将使用CRLF。文件的内容改变了——原来是LF,现在是CRLF,但是$ git status报告工作目录是干净的。这就导致了以下问题:如何提交行尾改变的文件?

为此,您必须使用工作目录中使用的行尾重新创建临时区域:

  • 关闭行尾转换$ git config --local core.autocrlf false
  • $ rm .git/index移除暂存区
  • $ git reset重新创建.git/index文件

现在$ git status打印工作目录脏的信息。您可以将带有CRLF行尾的linux/abcd.txt文件提交到数据库中。

13-5.将行尾转换为 LF 并提交更改

问题

您在一个包含不同新行编码的存储库中工作。你想:

  • 以使用LF行尾的方式转换工作目录中的文件
  • 将行尾转换为LF的文件作为新版本提交。

存储在 git 数据库中的对象应该包含LF行尾。

解决办法

从配方 13-1 克隆存储库:

$ cd git-recipes

$ git clone 13-02 13-05

$ cd 13-05

并遵循以下程序:

  1. 用一个规则* text=auto创建文件.gitattributes文件。你可以用$ echo "* text=auto" >>.gitattributes来做
  2. $ rm .git/index移除暂存区
  3. $ git reset重新创建.git/index文件

$ git status -sb检查所有文件的状态。如你所见,这次mixed/abcd.txtwindows/abcd.txt都被列为修改过的。使用以下命令完成提交所有已更改文件的配方:

$ git snapshot Standardization: line endings changed to LF.

所有三个快照HEAD、工作目录和暂存区现在都包含了LF行尾。配方 13-5 的结果总结在表 13-5 中。

表 13-5 。存储库 13-05 的最终状态中的三个快照中的行结束

image

注意,这个配方转换了文件mixed/abcd.txt

它是如何工作的

本配方中描述的程序使用以下.gitattributes条目:

* text=auto

由于上面的规则,当签入时,所有的文本文件都将被转换为使用LF

如果您用两个命令$ rm .git/index$ git reset重新创建暂存区,那么$ git status会通知您文件已经更改。下一次提交操作将在数据库中保存以LF行结尾的文件。

13-6.所有行尾的意外转换

问题

您希望了解如何避免在开源项目中无意转换所有行尾。为了更深入地了解这个问题,您希望重现这个失败。您的任务是在https://github.com/hakimel/reveal.js克隆一个托管在 github 上的reveal.js项目,然后以这样一种方式更改存储库配置,git 将认为所有文件都已更改。

解决办法

设置全局 git 配置,以便在签出时执行LF=>CRLF转换,在签入时执行CRLF=>LF转换:

$ git config --global core.autocrlf true

然后克隆reveal.js存储库:

$ cd git-recipes

$ git clone https://github.com/hakimel/reveal.js.git 13-06

$ cd 13-06

紧随克隆命令之后,13-06目录中的所有文本文件将使用CRLF行尾。无论您的操作系统是什么,都将使用这种编码。CRLF将用于 Windows、Linux 和 MacOS。存储库处于干净状态——您可以用$ git status -sb来验证它。

现在,使用以下命令关闭所有行尾转换:

$ git config --global core.autocrlf false

并使用以下内容重新创建临时区域:

$ rm .git/index

$ git reset

即使您的工作目录中的文件没有被改动,上面的更改也会让 git 感到困惑。命令$ git status -sb会通知你 git 认为所有的文本文件都被修改了。

您可以使用以下命令验证工作目录中的更改:

$ git diff --check

它将打印关于行尾变化的警告。

image 提示如果你用的是 Linux,可以跳过这两个命令:$ rm .git/index$ git reset在这个菜谱里;你会得到同样的结果。

它是如何工作的

项目reveal.js使用LF行尾。存储在对象数据库中的所有文本文件都使用行尾LF。当您在autocrlf设置为true的情况下执行克隆时,git 将会——在签出期间——执行LF=>CRLF转换;您的工作目录将包含带有CRLF的文件。但是,临时区域将使用原来的行尾,即LF

只要您打开了autocrlf, git 将在比较工作目录和临时区域时使用CRLF=>LF转换。因此,存储库保持干净。

如果使用$ git config --global core.autocrlf false关闭签入期间执行的转换,git 会将工作目录与临时区域进行比较,而不进行任何转换。因为存储在这两个位置的文件使用不同的行尾,所以$ git status命令报告在您的工作目录中有未暂存的更改。

这是你应该避免的情况。

想象现在在这种状态下你想为reveal.js做贡献。您更改了其中一个文件中的一行,然后用$ git add -A$ git commit命令提交更改。这个修订一旦被接受,将会引起项目领导和其他开发人员的头痛,因为它引入了数百个变更。除了一个以外,所有的都是不必要的,而且可能会被下一个使用不同行尾的投稿人恢复。

这个秘籍提供了一个你应该避免的模式。

13-7.为单个文件和目录定义行尾

问题

你开始一个新项目。您希望以这样的方式配置它:

  • 存储在linux/目录下的文本文件总是使用LF行尾。
  • 存储在windows/目录下的文本文件总是使用CRLF行尾。
  • 存储在mixed/目录下的文本文件永远不会被转换——它们总是保留原来的行尾。

解决办法

初始化新的存储库:

$ cd git-recipes

$ git init 13-07

$ cd 13-07

创建目录和文件:

$ mkdir linux
$ mkdir mixed
$ mkdir windows

$ printf "linux \n a \n b \n c \n d" > linux/abcd.txt
$ printf "mixed \n a \r\n b \n c \r\n d" > mixed/abcd.txt
$ printf "windows \r\n a \r\n b \r\n c \r\n d" > windows/abcd.txt

接下来创建包含以下内容的.gitattributes文件:

*           eol=lf
windows/*   eol=crlf
mixed/*     -text

最后,使用$ git snapshot Initial commit命令提交所有文件。

存储库现在包含了定义行尾转换的非常精确的规则。现在,如果有人克隆了这个库,那么不管他或她的设置是什么,克隆的库将包含我们在$ print命令中使用的完全相同的行尾。要验证这一点,用以下方法将core.autocrlf设置为true:

$ git config --global core.autocrlf true

然后克隆存储库:

$ cd ..

$ git clone 13-07 13-07-verification

$ cd 13-07-verification

命令:

$ hexdump -c linux/abcd.txt

打印带有LF行尾的文件内容。这证明即使core.autocrlf被设置为true,也没有执行转换。

它是如何工作的

规则* eol=lf强制 git 总是使用LF行尾签出所有文件。因此,默认情况下,所有文件都将使用LF编码。特别是存储在linux/目录下的文件。第二条规则是windows/* eol=crlf,定义了第一条规则的例外。当检出存储在windows/目录下的文件时,将使用CRLF。最后一个规则mixed/* -text,关闭所有保存在mixed/目录下的文件的行尾转换。

写在.gitattributes文件中的配置覆盖用$ git config命令定义的设置。因此,无论您的设置是什么,项目的工作目录将始终遵循预定义的假设:

  • 存储在windows/下的所有文本文件都将使用CRLF
  • 存储在mixed/下的所有文本文件将始终保留原始行尾
  • 所有其他文本文件将使用LF

image 提示这个解决方案用在一个 jQuery 项目中。由于存储在.gitattributes文件中的* eol=lf规则,所有文本文件总是使用LF作为行尾字符进行编码;无论您的平台和配置如何。

13-8.忽略自动生成的文件

问题

您开始了一个新项目,其中一些工具会生成临时文件。您不希望将它们提交到存储库中。项目中的临时文件符合以下规则:

  • 它们存储在/ tmp/目录中。
  • 它们的名称以扩展名.abc结尾。

因此,您希望忽略与以下两种模式匹配的文件:

/tmp/
*.abc

您希望与参与此项目的所有开发人员共享忽略文件的规则。

解决办法

初始化新的存储库:

$ cd git-recipes

$ mkdir 13-08

$ cd 13-08

$ git init

使用以下内容创建一个空的初始版本:

$ git commit --allow-empty -m "Initial commit"

用以下内容创建名为.gitignore的文件:

/tmp/
*.abc

您可以使用以下两个命令来完成此操作:

echo /tmp/ > .gitignore
echo "*.abc" >> .gitignore

使用以下命令将文件.gitignore提交到存储库中:

$ git add -A
$ git commit -m "Gitignore: new rules to ignore files"

存储库已准备就绪;可以分享给其他开发者。

要测试存储在/tmp/目录中的文件和扩展名为.abc的文件是否真的被忽略,请创建两个文件:

$ echo abc > some-file.abc

$ mkdir tmp

$ echo def > tmp/some-file.txt

并使用$ git status命令检查存储库的状态。$ git status不会报告与.gitignore文件中定义的模式匹配的文件。

它是如何工作的

如果您的项目包含一些自动生成的文件,您可能应该忽略它们。最好的方法是创建一个名为.gitignore的特殊文件。该文件应该包含 git 将忽略的模式。因此,如果您将文件提交到您的存储库中,您在同一个项目中工作的所有同事将共享这些规则。

存储在.gitignore中的规则如下。

  • 如果规则以斜杠/开始,它将只匹配存储在项目根目录中的条目。规则/foo只会匹配文件/foo,而不会匹配文件some/dir/foo
  • 如果规则以斜杠/结束,它将只匹配目录。因此,规则bar/将匹配目录bar/some/other/dir/bar/,但不匹配文件some/special/bar

您可以基于每个目录使用.gitignore文件。目录中存储的.gitignore文件会影响该目录及其子目录。

三种类型的设置

应该忽略的文件模式可以在三个不同的级别上定义:

  • .gitignore—该文件被提交到目录中;它只影响提交它的存储库。
  • 全局.gitignore—该文件驻留在您的主目录中。它会影响您所有的存储库。这是您的私有文件:您不需要将该文件提交到存储库中。
  • .git/info/exclude—该文件存储在。git 目录;这是你的私人文件,你不能与他人分享。exclude文件只影响一个存储库:包含该文件的存储库。

如何清理包含被忽略文件的项目?

如果存储库包含被忽略的文件,您可以使用以下命令删除所有被跟踪的文件:

$ git ls-files | xargs rm

如果您想删除所有未被跟踪的文件,请使用:

$ git clean -f

13-9.使用自定义项目。分发文件

问题

您想要启动一个新的互联网应用来发布博客。您计划将应用作为开放源代码发布。博客条目将存储在数据库中,访问数据库服务器的凭证将存储在一个文件中。

为了让计划使用您的应用的人的生活更轻松,您需要:

  • 定义忽略配置文件的规则
  • 创建配置文件的一般结构

.gitignore 文件和通用配置文件都应该与应用的代码一起提交。

解决办法

初始化新的存储库:

$ cd git-recipes

$ mkdir 13-09

$ cd 13-09

$ git init

使用以下内容创建一个空的初始版本:

$ git commit --allow-empty -m "Initial commit"

创建名为database.ini-dist的配置文件。文件的内容显示在清单 13-5 中。

清单 13-5。 配置文件 database.ini-dist

[parameters]
    database_host     = your.host.example.net
    database_name     = dbname
    database_user     = admin
    database_password = sEcrEtPaSSword

创建包含单个规则的.gitignore文件:

/database.ini

您可以使用以下命令生成该文件:

$ echo /database.ini > .gitignore

提交两个文件:

$ git add -A
$ git commit -m "Generic database configuration"

它是如何工作的

如果有人想使用你的应用,他必须克隆它并定制配置。用户必须将文件database.ini-dist重命名为database.ini,并根据自己的设置更改其内容。多亏了.gitignore文件,他的个人设置永远不会被提交到存储库中。

13-10.使用。git/info/exclude 文件

问题

您希望使用 NetBeans IDE 为开源项目http://github.com/symfony/symfony.git做出贡献。

解决办法

克隆您要参与的存储库:

$ cd git-recipes

$ mkdir 13-10

$ cd 13-10

$ git clone http://github.com/symfony/symfony.git .

当您使用 NetBeans 打开一个新项目时,IDE 会在项目的根目录下创建nbproject/目录。为了避免提交目录nbproject/,在.git/info/exclude文件中创建以下条目:

/nbproject/

您可以使用以下命令来完成此操作:

$ echo /nbproject/ > .git/info/exclude

现在启动 NetBeans 并打开您刚刚克隆的项目。IDE 将创建它的/nbproject/目录,但是由于模式/nbproject/存储在.git/info/exclude文件中,这个库保持干净。命令:

$ git status -sb

不报告/nbproject/目录内的更改。

它是如何工作的

许多现代的 ide 使用特殊的目录来存储每个项目的配置。NetBeans 将其配置存储在/nbproject/目录中,JetBrains 生产的 PhpStorm 和其他工具将配置存储在/.idea/目录中。因为每个开发人员可以使用不同的工具和编辑器,所以这些文件和目录通常不会在项目中提交。

因为默认情况下配置存储在存储库的工作目录中,git 将用$ git status 命令报告这些文件。为了避免这种情况,您可以使用.git/info/exclude或您的个人.gitignore文件忽略配置文件。

如果您选择忽略带有.git/info/exclude文件的配置,您将不得不在每个新项目中定义模式/nbproject/

如果您选择使用您的个人.gitignore文件,您可以定义将在您的所有项目中使用的模式。

13-11.使用标签

问题

您希望使用标签来标记项目的发布。

解决办法

初始化新的存储库:

$ cd git-recipes

$ mkdir 13-11

$ cd 13-11

$ git init

使用以下内容创建项目历史:

$ git simple-commit a b c d

现在您想用v1.2.3 标记项目的当前状态。您可以使用以下命令来完成此操作:

$ git tag -a v1.2.3 -m "Release 1.2.3"

使用以下内容创建更多提交:

$ git simple-commit e f g

该州尚未准备好下一个版本。然而,出于某种原因,您希望使用轻量级标签来保留对最近提交的引用。为此,执行以下命令:

$ git tag temp-version

您的存储库现在包含七个提交abcdfg和两个标记v1.2.3temp-version

它是如何工作的

Git 允许您用标签标记任意的修订。有两种类型的标签:

  • 带注释的
  • 轻量级选手

带注释的标签作为对象存储在您的存储库中。它们包含以下信息:

  • 作者
  • 标签的创建日期
  • 评论
  • 被标记的修订的 SHA-1

轻量级标签只包含它们所指向的版本的 SHA-1。

您可以使用以下命令列出所有标记,包括带注释的和轻量级的:

$ git tag

这两种类型的标签都存储在.git/refs/tags目录中。您的.git/refs/tags存储库现在包含两个文件。您可以通过以下方式进行检查:

$ ls .git/refs/tags

存储在.git/refs/tags中的文件包含 SHA-1 散列。在轻量级标签的情况下,这个散列指向修订。对于带注释的标签,散列指向存储在数据库中的标签对象。

命令:

$ git show -s tagname

打印标签的详细信息。对带注释的标签执行时:

$ git show -s v1.2.3

打印关于标签对象的详细信息:

  • 标记名(标记的 SHA-1 散列)
  • 标记者(创建标记的人)
  • 日期
  • 评论
  • 修订本

下面是示例输出:

tag v1.2.3
Tagger: Włodzimierz Gajda <gajdaw@gajdaw.pl>
Date:   Sun Nov 3 10:32:10 2013 +0100

Release 1.2.3

commit b2e1f624d8c7ce5e6a0917ed55d3bfc69bbefd9e

当用于轻量级标签时,该命令仅产生提交的数据:

commit e2833c1517a3873661a35f808349b473f56aff7c
Author: Włodzimierz Gajda <gajdaw@gajdaw.pl>
Date:   Sun Nov 3 10:33:32 2013 +0100

创建、删除和列出标签

要创建带注释的标签,请使用:

$ git tag -a tag-name -m "tag comment" [REVISION]

轻量级标签由以下各项创建:

$ git tag tag-name [REVISION]

要删除带注释的标签和轻量级标签,请使用:

$ git tag -d tag-name

您可以使用以下选项列出标签:

$ git tag

如果您想列出所有按日期排序的标签,请使用以下命令:

$ git log --tags --simplify-by-decoration --pretty="%ai %d"

下面是检查最新注释标签的命令:

$ git describe

发布标签

您可以通过以下方式发布您的标签:

$ git push --tags

每当执行该命令时:

$ git fetch

它将从服务器获取所有的标签。

如果出于任何原因您想要删除远程标签,您应该使用:

$ git push origin :refs/tags/tag-name

与分支类似,当远程存储库中的标签被删除时,您的本地存储库不会受到影响。命令$ git fetch 将获取任何新的标记,但不会删除远程存储库中已删除的任何标记。要将您的标签与远程标签同步,请使用:

$ git fetch --tags

使用标签

标签可以像任何其他版本的标识符一样使用。您可以将它们传递给$ git branch$ git checkout、和$ git reset、命令,例如:

$ git reset --hard v1.2.3
$ git checkout -b my-new-branch v1.2.3

image 提示你可以把标签当成不能动的树枝。

13-12.将存储库导出到压缩档案

问题

您从事的项目刚刚发布了一个稳定的版本。您希望生成一个压缩的归档文件,其中包含对希望使用您的项目的任何人都很重要的所有文件。您不希望在压缩的档案中包含仅对参与该项目的开发人员重要的文件。

解决办法

初始化新的存储库:

$ cd git-recipes

$ mkdir 13-12

$ cd 13-12

$ git init

使用以下内容创建一个空的初始版本:

$ git commit --allow-empty -m "Initial commit"

现在创建目录src/doc/,并在其中提交一些文件:

$ mkdir src

$ echo "/* code */" > src/main.c

$ mkdir doc

$ echo "<DOCTYPE html>" > doc/index.html

$ git add -A

$ git commit -m "Source code and documentation"

最后,在Test/目录中创建一些测试:

$ mkdir Tests

$ echo "/* tests */" > Tests/TestMain.c

$ git add -A

$ git commit -m "Tests"

现在您的项目包含三个目录:src/doc/Tests/。存储在src/doc/中的文件对您项目的用户来说很重要。存储在Tests/目录中的文件只对参与您的项目的开发人员重要。对于任何想使用你的项目的人来说,它们并不重要。

用以下内容创建名为.gitattributes的文件:

/Tests/ export-ignore
/.gitattributes export-ignore

您可以使用以下命令来完成此操作:

$ echo "/Tests/ export-ignore" > .gitattributes

$ echo "/.gitattributes export-ignore" >> .gitattributes

提交此文件:

$ git add .gitattributes
$ git commit -m "Gitattributes to exclude /Tests/ and /.gitattributes from ZIP"

该项目已经达到了历史上的一个稳定点。使用以下命令将其标记为 v2.3.4:

$ git tag -a v2.3.4 -m "Release 2.3.4"

最后,生成包含项目版本 2.3.4 的压缩归档文件:

$ git archive --format=zip --output=../project-v2.3.4.zip v2.3.4

该命令将创建文件git-recipes/project-v2.3.4.zip。该文件将包含您的项目,但没有Tests/目录和.gitattributes文件。要列出压缩文件的内容,可以使用以下命令:

$ unzip -l ../project-v2.3.4.zip

它是如何工作的

命令:

$ git archive --format=zip --output=filename.zip [REVISION]

将保存在[REVISION]中的版本的项目导出到名为filename.zip的文件中。该文件以 ZIP 格式存储。由于有了这个命令,您不必创建项目的压缩版本,例如:

project-v0.1.2.zip
project-v2.8.4.zip
project-v5.15.89.zip

将项目保留在特定版本中。您所要做的就是创建标签,例如:

$ git tag -a v0.1.2 -m "Release 0.1.2"
$ git tag -a v2.8.4 -m "Release 2.8.4"
$ git tag -a v5.15.89 -m "Release 5.15.89"

如果您想获得特定版本的压缩文件,您只需执行一个命令,例如:

$ git archive --format=zip --output=project-v0.1.2.zip v0.1.2
$ git archive --format=zip --output=project-v2.8.4.zip v2.8.4
$ git archive --format=zip --output=project-v5.15.89.zip v5.15.89

所有版本都存储在同一个存储库中,可以使用$ git archive命令按需生成。不需要备份project-v2.8.4.zip等文件。如果您备份了您的存储库,您将始终能够生成所有被标记的特定版本。

Gitattribute file 允许您从自动生成的归档文件中排除一些文件。当.gitattributes文件包含以下规则时:

/Tests/ export-ignore

那么生成的档案将包含除了存储在/Tests/目录中的文件之外的所有文件。

摘要

本章介绍的第一个命令$ git diff,将帮助您检查项目的状态。它使用 GNU diffutils 工具定义的格式报告变化。默认情况下,在没有参数的情况下调用时:

$ git diff

将工作目录与临时区域进行比较。使用--staged参数,您可以将暂存区与HEAD进行比较:

$ git diff --staged

使用两个修订版调用 git diff,比较这些修订版中的文件:

$ git diff master beta

附加参数--unified可用于指定将要打印的未更改行数。

配方 13-2 至 13-7 提出了各种有关行尾的问题。因为 git 是一个同步一组开发人员工作的工具,并且因为每个开发人员可以使用不同的平台,所以您必须意识到可能会使团队工作复杂化的问题。

讨论的第一个项目是如何提交以及如何在 git 不执行任何转换的情况下,完全按照文件的原样签出文件。这些在秘籍 13-2 和 13-3 中提到的事情,在你对你的行尾有问题并想摆脱它们的时候是必不可少的。请记住,要获得干净的签出,您可以删除跟踪的文件:

$ git ls-files | xargs rm

和集结地:

$ rm .git/index

命令$ git reset --hard使用存储在HEAD中的快照重新创建工作目录和暂存区。

秘籍 13-4 和 13-5 更详细地解释了如果你想提交行尾分别转换成 CRLF 和 LF 的文件,你应该遵循的步骤。

秘籍 13-6 提出了一个反模式,应该让你相信行尾是多么重要。当遵循时,它将产生以下错误:即使没有文件被编辑,git 也将它们报告为全部被更改。如果任何一个开发人员提交这种类型的变更,将会使其他开发人员感到困惑。顺便说一下,$ git diff命令在处理关于白色字符,尤其是行尾的问题时也很有用。用--check参数调用时:

$ git diff --check

输出可被视为白色字符问题的更改。记住,防止所有行尾问题的最好和最简单的解决方案之一是使用.gitattributes文件——在每个模式的基础上定义行尾。使用简单的规则* eol=lf,你可以避免所有关于行尾的问题。该解决方案在配方 13-7 中给出。

配方 13-8、13-9 和 13-10 给出了三个最典型的问题,通过定义适当的规则来忽略文件,可以解决这些问题。第一个问题涉及由各种工具自动生成的二进制文件。以下是一些例子:

  • 编译时生成的文件:*.oa.out*.exe*.lib
  • 执行应用时生成的缓存配置;它可以存储在一些特殊的目录中,比如cache/
  • 依赖项—嵌入到项目中的外部库;它们可以存储在一些特定的目录中,比如vendor/

因为所有开发人员都会不时地生成上述所有文件,所以对于这些文件,最好的解决方案是使用项目提交的.gitignore文件来忽略它们。

第二种情况与包含一些私人信息的文件有关。它可以是具有访问外部资源(如数据库)的凭证的配置。在这里,所有的开发者和用户都将面临同样的问题:如何避免无意中发布这些敏感数据。因此,排除这些类型文件的规则也应该存储在提交到存储库中的.gitignore文件中。

第三种情况涉及到每个开发者的私有设置。我使用特定的工具在项目中创建特定的文件。在这种情况下,将规则提交到存储库中是没有意义的。因此,要忽略由我使用的工具生成的文件和目录,我可以使用存储在特定存储库中的.git/info/exclude文件,也可以使用存储在用户主目录中的全局.gitignore文件。

两个最终配方展示了如何使用标签和$ git archive命令。记住 git 提供了两种不同类型的标签:带注释的标签和轻量级标签。带注释的标签存储在.git/objects数据库中。它们包含关于谁、何时以及为什么创建标签的详细信息。轻量级标签只是引用提交的别名。与分支的情况一样:没有关于谁、何时或为什么创建了轻量级标签或分支的信息。有些项目,比如 jQuery,使用轻量级标签。其他的,比如 Symfony,使用带注释的标签。选择由您决定,尽管因为带注释的标签包含作者、日期和评论,它们通常被认为是更好的选择。这两种类型的标签都是通过以下方式发布的:

$ git push --tags

要将您的标签与远程存储库同步,请使用:

$ git fetch --tags

要删除远程标记,您应该使用与删除远程分支相同的命令:

$ git push origin :remote-branch-to-delete
$ git push origin :refs/tags/remote-tag-to-delete