diff --git a/book/01-introduction/sections/about-version-control.asc b/book/01-introduction/sections/about-version-control.asc index 83722d03..3ee1a58a 100644 --- a/book/01-introduction/sections/about-version-control.asc +++ b/book/01-introduction/sections/about-version-control.asc @@ -23,8 +23,7 @@ image::images/local.png[本地版本控制图解] 其中最流行的一种叫做 RCS,现今许多计算机系统上都还看得到它的踪影。 -https://www.gnu.org/software/rcs/[RCS] -的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。 +https://www.gnu.org/software/rcs/[RCS^] 的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。 ==== 集中化的版本控制系统 @@ -51,8 +50,7 @@ image::images/centralized.png[集中化的版本控制图解] (((version control,distributed))) 于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 -在这类系统中,像 Git、Mercurial 以及 Darcs 等,客户端并不只提取最新版本的文件快照, -而是把代码仓库完整地镜像下来,包括完整的历史记录。 +在这类系统中,像 Git、Mercurial 以及 Darcs 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。 diff --git a/book/01-introduction/sections/first-time-setup.asc b/book/01-introduction/sections/first-time-setup.asc index 90c76a49..6cb49cee 100644 --- a/book/01-introduction/sections/first-time-setup.asc +++ b/book/01-introduction/sections/first-time-setup.asc @@ -21,9 +21,8 @@ Git 自带一个 `git config` 的工具来帮助设置控制 Git 外观和行为 在 Windows 系统中,Git 会查找 `$HOME` 目录下(一般情况下是 `C:\Users\$USER` )的 `.gitconfig` 文件。 Git 同样也会寻找 `/etc/gitconfig` 文件,但只限于 MSys 的根目录下,即安装 Git 时所选的目标位置。 -如果你在 Windows 上使用 Git 2.x 以后的版本,那么还有一个系统级的配置文件,Windows XP 上在 -`C:\Documents and Settings\All Users\Application Data\Git\config` ,Windows Vista 及其以后的版本在 -`C:\ProgramData\Git\config` 。此文件只能以管理员权限通过 `git config -f ` 来修改。 +如果你在 Windows 上使用 Git 2.x 以后的版本,那么还有一个系统级的配置文件,Windows XP 上在 `C:\Documents and Settings\All Users\Application Data\Git\config` ,Windows Vista 及其以后的版本在 `C:\ProgramData\Git\config` 。 +此文件只能以管理员权限通过 `git config -f ` 来修改。 你可以通过以下命令查看所有的配置以及它们所在的文件: @@ -64,8 +63,7 @@ $ git config --global core.editor emacs 在 Windows 系统上,如果你想要使用别的文本编辑器,那么必须指定可执行文件的完整路径。 它可能随你的编辑器的打包方式而不同。 -对于 Notepad++,一个流行的代码编辑器来说,你可能想要使用 32 位的版本, -因为在本书编写时 64 位的版本尚不支持所有的插件。 +对于 Notepad++,一个流行的代码编辑器来说,你可能想要使用 32 位的版本,因为在本书编写时 64 位的版本尚不支持所有的插件。 如果你在使用 32 位的 Windows 系统,或在 64 位系统上使用 64 位的编辑器,那么你需要输入如下命令: [source,console] @@ -75,10 +73,8 @@ $ git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -m [NOTE] ==== -Vim、Emacs 和 Notepad++ 都是流行的文本编辑器,通常程序员们会在 Linux 和 macOS -这类基于 Unix 的系统或 Windows 系统上使用它们。 -如果你在使用其他的或 32 位版本的编辑器,请在 <> -中查看设置为该编辑器的具体步骤。 +Vim、Emacs 和 Notepad++ 都是流行的文本编辑器,通常程序员们会在 Linux 和 macOS 这类基于 Unix 的系统或 Windows 系统上使用它们。 +如果你在使用其他的或 32 位版本的编辑器,请在 <> 中查看设置为该编辑器的具体步骤。 ==== [WARNING] @@ -87,6 +83,19 @@ Vim、Emacs 和 Notepad++ 都是流行的文本编辑器,通常程序员们会 例如,在 Windows 上 Git 在开始编辑时可能会过早地结束。 ==== +[[_new_default_branch]] +==== 默认分支 + +默认情况下,当你使用 `git init` 创建新仓库时,Git 会创建一个名为 _master_ 的分支。 +从 Git 2.28 版本开始,你可以为初始分支设置不同的名称。 + +要将 _main_ 设置为默认分支名称,请执行: + +[source,console] +---- +$ git config --global init.defaultBranch main +---- + ==== 检查配置信息 如果想要检查你的配置,可以使用 `git config --list` 命令来列出所有 Git 当时能找到的配置: diff --git a/book/01-introduction/sections/help.asc b/book/01-introduction/sections/help.asc index 703a7210..3c5b87ff 100644 --- a/book/01-introduction/sections/help.asc +++ b/book/01-introduction/sections/help.asc @@ -18,7 +18,7 @@ $ git help config ---- 这些命令很棒,因为你随时随地可以使用而无需联网。 -如果你觉得手册或者本书的内容还不够用,你可以尝试在 Freenode IRC 服务器 https://freenode.net[] 上的 `#git` 或 `#github` 频道寻求帮助。 +如果你觉得手册或者本书的内容还不够用,你可以尝试在 Libera Chat IRC 服务器 https://libera.chat/[^] 上的 `#git`、`#github` 或 `#gitlab` 频道寻求帮助。 这些频道经常有上百人在线,他们都精通 Git 并且乐于助人。(((IRC))) 此外,如果你不需要全面的手册,只需要可用选项的快速参考,那么可以用 `-h` 选项获得更简明的 ``help'' 输出: @@ -31,18 +31,20 @@ usage: git add [] [--] ... -n, --dry-run dry run -v, --verbose be verbose - -i, --interactive interactive picking - -p, --patch select hunks interactively - -e, --edit edit current diff and apply - -f, --force allow adding otherwise ignored files - -u, --update update tracked files - --renormalize renormalize EOL of tracked files (implies -u) - -N, --intent-to-add record only the fact that the path will be added later - -A, --all add changes from all tracked and untracked files - --ignore-removal ignore paths removed in the working tree (same as --no-all) - --refresh don't add, only refresh the index - --ignore-errors just skip files which cannot be added because of errors - --ignore-missing check if - even missing - files are ignored in dry run - --chmod (+|-)x override the executable bit of the listed files + -i, --interactive interactive picking + -p, --patch select hunks interactively + -e, --edit edit current diff and apply + -f, --force allow adding otherwise ignored files + -u, --update update tracked files + --renormalize renormalize EOL of tracked files (implies -u) + -N, --intent-to-add record only the fact that the path will be added later + -A, --all add changes from all tracked and untracked files + --ignore-removal ignore paths removed in the working tree (same as --no-all) + --refresh don't add, only refresh the index + --ignore-errors just skip files which cannot be added because of errors + --ignore-missing check if - even missing - files are ignored in dry run + --sparse allow updating entries outside of the sparse-checkout cone + --chmod (+|-)x override the executable bit of the listed files + --pathspec-from-file read pathspec from file + --pathspec-file-nul with --pathspec-from-file, pathspec elements are separated with NUL character ---- - diff --git a/book/01-introduction/sections/installing.asc b/book/01-introduction/sections/installing.asc index 86d73310..2d306f2a 100644 --- a/book/01-introduction/sections/installing.asc +++ b/book/01-introduction/sections/installing.asc @@ -29,7 +29,7 @@ $ sudo dnf install git-all $ sudo apt install git-all ---- -要了解更多选择,Git 官方网站上有在各种 Unix 发行版的系统上安装步骤,网址为 https://git-scm.com/download/linux[]。 +要了解更多选择,Git 官方网站上有在各种 Unix 发行版的系统上安装步骤,网址为 https://git-scm.com/download/linux[^]。 ==== 在 macOS 上安装 @@ -46,40 +46,28 @@ $ git --version 如果没有安装过命令行开发者工具,将会提示你安装。 如果你想安装更新的版本,可以使用二进制安装程序。 -官方维护的 macOS Git 安装程序可以在 Git 官方网站下载,网址为 https://git-scm.com/download/mac[]。 +官方维护的 macOS Git 安装程序可以在 Git 官方网站下载,网址为 https://git-scm.com/download/mac[^]。 .Git macOS Installer. image::images/git-osx-installer.png[Git macOS 安装程序。] -你也可以将它作为 GitHub for macOS 的一部分来安装。 -它们的图形化 Git 工具有一个安装命令行工具的选项。 -你可以从 GitHub for macOS 网站下载该工具,网址为 https://mac.github.com[]。 - ==== 在 Windows 上安装 在 Windows 上安装 Git 也有几种安装方法。(((Windows, installing))) 官方版本可以在 Git 官方网站下载。 -打开 https://git-scm.com/download/win[],下载会自动开始。 -要注意这是一个名为 Git for Windows 的项目(也叫做 msysGit),和 Git 是分别独立的项目;更多信息请访问 http://msysgit.github.io/[]。 +打开 https://git-scm.com/download/win[^],下载会自动开始。 +要注意这是一个名为 Git for Windows 的项目(也叫做 msysGit),和 Git 是分别独立的项目;更多信息请访问 https://gitforwindows.org[^]。 -要进行自动安装,你可以使用 https://chocolatey.org/packages/git[Git Chocolatey 包]。 +要进行自动安装,你可以使用 https://community.chocolatey.org/packages/git[Git Chocolatey 包^]。 注意 Chocolatey 包是由社区维护的。 -另一个简单的方法是安装 GitHub Desktop。 -该安装程序包含图形化和命令行版本的 Git。 -它也能支持 Powershell,提供了稳定的凭证缓存和健全的换行设置。(((Powershell)))(((CRLF)))(((credential caching))) -稍后我们会对这方面有更多了解,现在只要一句话就够了,这些都是你所需要的。 -你可以在 GitHub for Windows 网站下载,网址为 https://desktop.github.com[GitHub Desktop 网站]。 - - ==== 从源代码安装 有人觉得从源码安装 Git 更实用,因为你能得到最新的版本。 二进制安装程序倾向于有一些滞后,当然近几年 Git 已经成熟,这个差异不再显著。 如果你想从源码安装 Git,需要安装 Git 依赖的库:autotools、curl、zlib、openssl、expat 和 libiconv。 -如果你的系统上有 `dnf` (如 Fedora)或者 `apt`(如基于 Debian 的系统), -可以使用对应的命令来安装最少的依赖以便编译并安装 Git 的二进制版: +如果你的系统上有 `dnf` (如 Fedora)或者 `apt`(如基于 Debian 的系统),可以使用对应的命令来安装最少的依赖以便编译并安装 Git 的二进制版: [source,console] ---- @@ -99,9 +87,7 @@ $ sudo apt-get install asciidoc xmlto docbook2x [NOTE] ==== -使用 RHEL 和 RHEL 衍生版,如 CentOS 和 Scientific Linux 的用户需要 -https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F[开启 EPEL 库] -以便下载 `docbook2X` 包。 +使用 RHEL 和 RHEL 衍生版,如 CentOS 和 Scientific Linux 的用户需要 https://docs.fedoraproject.org/en-US/epel/#how_can_i_use_these_extra_packages[开启 EPEL 库^] 以便下载 `docbook2X` 包。 ==== 如果你使用基于 Debian 的发行版(Debian/Ubuntu/Ubuntu-derivatives),你也需要 `install-info` 包: @@ -111,8 +97,7 @@ https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F[开启 $ sudo apt-get install install-info ---- -如果你使用基于 RPM 的发行版(Fedora/RHEL/RHEL衍生版),你还需要 `getopt` 包 -(它已经在基于 Debian 的发行版中预装了): +如果你使用基于 RPM 的发行版(Fedora/RHEL/RHEL衍生版),你还需要 `getopt` 包(它已经在基于 Debian 的发行版中预装了): [source,console] ---- @@ -129,8 +114,7 @@ $ sudo ln -s /usr/bin/db2x_docbook2texi /usr/bin/docbook2x-texi 以此来解决二进制文件名的不同。 当你安装好所有的必要依赖,你可以继续从几个地方来取得最新发布版本的 tar 包。 -你可以从 Kernel.org 网站获取,网址为 https://www.kernel.org/pub/software/scm/git[], -或从 GitHub 网站上的镜像来获得,网址为 https://github.com/git/git/releases[]。 +你可以从 Kernel.org 网站获取,网址为 https://www.kernel.org/pub/software/scm/git[^],或从 GitHub 网站上的镜像来获得,网址为 https://github.com/git/git/tags[^]。 通常在 GitHub 上的是最新版本,但 kernel.org 上包含有文件下载签名,如果你想验证下载正确性的话会用到。 接着,编译并安装: @@ -149,5 +133,5 @@ $ sudo make install install-doc install-html install-info [source,console] ---- -$ git clone git://git.kernel.org/pub/scm/git/git.git +$ git clone https://git.kernel.org/pub/scm/git/git.git ---- diff --git a/book/01-introduction/sections/what-is-git.asc b/book/01-introduction/sections/what-is-git.asc index bc6ea63e..0d13e8ab 100644 --- a/book/01-introduction/sections/what-is-git.asc +++ b/book/01-introduction/sections/what-is-git.asc @@ -1,10 +1,10 @@ +[[what_is_git_section]] === Git 是什么? 那么,简单地说,Git 究竟是怎样的一个系统呢? 请注意接下来的内容非常重要,若你理解了 Git 的思想和基本工作原理,用起来就会知其所以然,游刃有余。 -在学习 Git 时,请尽量理清你对其它版本管理系统已有的认识,如 CVS、Subversion 或 Perforce, -这样能帮助你使用工具时避免发生混淆。尽管 Git 用起来与其它的版本控制系统非常相似, -但它在对信息的存储和认知方式上却有很大差异,理解这些差异将有助于避免使用中的困惑。(((Subversion)))(((Perforce))) +在学习 Git 时,请尽量理清你对其它版本管理系统已有的认识,如 CVS、Subversion 或 Perforce,这样能帮助你使用工具时避免发生混淆。 +尽管 Git 用起来与其它的版本控制系统非常相似,但它在对信息的存储和认知方式上却有很大差异,理解这些差异将有助于避免使用中的困惑。(((Subversion)))(((Perforce))) ==== 直接记录快照,而非差异比较 @@ -36,15 +36,14 @@ Git 更像是一个小型的文件系统,提供了许多以此为基础构建 因为你在本地磁盘上就有项目的完整历史,所以大部分操作看起来瞬间完成。 举个例子,要浏览项目的历史,Git 不需外连到服务器去获取历史,然后再显示出来——它只需直接从本地数据库中读取。 -你能立即看到项目历史。如果你想查看当前版本与一个月前的版本之间引入的修改, -Git 会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再来本地处理。 +你能立即看到项目历史。 +如果你想查看当前版本与一个月前的版本之间引入的修改,Git 会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再来本地处理。 这也意味着你在离线或者没有 VPN 时,几乎可以进行任何操作。 -如你在飞机或火车上想做些工作,就能愉快地提交(到你的 *本地* 副本,还记得吗?), -直到有网络连接时再上传。如你回家后 VPN 客户端不正常,那么也仍能工作。 +如你在飞机或火车上想做些工作,就能愉快地提交(到你的 *本地* 副本,还记得吗?),直到有网络连接时再上传。 +如你回家后 VPN 客户端不正常,那么也仍能工作。 使用其它系统的话,做到这些是不可能或很费力的。 -比如,用 Perforce 的话,没有连接服务器时几乎不能做什么事;而用 Subversion 和 CVS 的话, -你能修改文件,但不能向数据库提交修改(因为你的本地数据库离线了)。 +比如,用 Perforce 的话,没有连接服务器时几乎不能做什么事;而用 Subversion 和 CVS 的话,你能修改文件,但不能向数据库提交修改(因为你的本地数据库离线了)。 这样似乎问题不大,但是你可能会惊喜地发现它带来的巨大的不同。 ==== Git 保证完整性 @@ -70,8 +69,7 @@ Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值 你执行的 Git 操作,几乎只往 Git 数据库中 *添加* 数据。 你很难使用 Git 从数据库中删除数据,也就是说 Git 几乎不会执行任何可能导致文件不可恢复的操作。 -同别的 VCS 一样,未提交更新时有可能丢失或弄乱修改的内容。但是一旦你提交快照到 Git 中, -就难以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。 +同别的 VCS 一样,未提交更新时有可能丢失或弄乱修改的内容。但是一旦你提交快照到 Git 中,就难以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。 这使得我们使用 Git 成为一个安心愉悦的过程,因为我们深知可以尽情做各种尝试,而没有把事情弄糟的危险。 更深度探讨 Git 如何保存数据及恢复丢失数据的话题,请参考<>。 diff --git a/book/02-git-basics/sections/getting-a-repository.asc b/book/02-git-basics/sections/getting-a-repository.asc index 37ce5985..23052b94 100644 --- a/book/02-git-basics/sections/getting-a-repository.asc +++ b/book/02-git-basics/sections/getting-a-repository.asc @@ -60,8 +60,7 @@ $ git commit -m 'initial project version' 如果你对其它的 VCS 系统(比如说 Subversion)很熟悉,请留心一下你所使用的命令是"clone"而不是"checkout"。 这是 Git 区别于其它版本控制系统的一个重要特性,Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。 当你执行 `git clone` 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。 -事实上,如果你的服务器的磁盘坏掉了,你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库 -(虽然可能会丢失某些服务器端的钩子(hook)设置,但是所有版本的数据仍在,详见 <> )。 +事实上,如果你的服务器的磁盘坏掉了,你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库(虽然可能会丢失某些服务器端的钩子(hook)设置,但是所有版本的数据仍在,详见 <> )。 克隆仓库的命令是 `git clone ` 。(((git commands, clone))) 比如,要克隆 Git 的链接库 `libgit2`,可以用下面的命令: @@ -71,8 +70,7 @@ $ git commit -m 'initial project version' $ git clone https://github.com/libgit2/libgit2 ---- -这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 `.git` 文件夹, -从远程仓库拉取下所有数据放入 `.git` 文件夹,然后从中读取最新版本的文件的拷贝。 +这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 `.git` 文件夹,从远程仓库拉取下所有数据放入 `.git` 文件夹,然后从中读取最新版本的文件的拷贝。 如果你进入到这个新建的 `libgit2` 文件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。 如果你想在克隆远程仓库的时候,自定义本地仓库的名字,你可以通过额外的参数指定新的目录名: diff --git a/book/02-git-basics/sections/recording-changes.asc b/book/02-git-basics/sections/recording-changes.asc index aabac575..16ba630e 100644 --- a/book/02-git-basics/sections/recording-changes.asc +++ b/book/02-git-basics/sections/recording-changes.asc @@ -8,8 +8,7 @@ 它们的状态可能是未修改,已修改或已放入暂存区。简而言之,已跟踪的文件就是 Git 已经知道的文件。 工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。 -初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们, -而你尚未编辑过它们。 +初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们,而你尚未编辑过它们。 编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。 在工作时,你可以选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改,如此反复。 @@ -37,6 +36,15 @@ nothing to commit, working directory clean 现在,分支名是“master”,这是默认的分支名。 我们在 <> 中会详细讨论分支和引用。 +[NOTE] +==== +GitHub 在2020年年中将默认分支名称从 `master` 改为 `main`,其他 Git 服务提供商也随后跟进。 +因此,你可能会发现一些新创建的仓库中,默认分支名称是 `main` 而不是 `master`。 +此外,可以更改默认分支名称(如您在 <> 中所见),因此您可能会看到默认分支的名称不同。 + +不过,Git 本身仍然将 `master` 作为默认分支,因此在本书中我们将一直使用它。 +==== + 现在,让我们在项目下创建一个新的 `README` 文件。 如果之前并不存在这个文件,使用 `git status` 命令,你将看到一个新的未跟踪文件: @@ -86,8 +94,7 @@ Changes to be committed: 只要在 `Changes to be committed` 这行下面的,就说明是已暂存状态。 如果此时提交,那么该文件在你运行 `git add` 时的版本将被留存在后续的历史记录中。 -你可能会想起之前我们使用 `git init` 后就运行了 `git add ` 命令,开始跟踪当前目录下的文件。 -(((git commands, init)))(((git commands, add))) +你可能会想起之前我们使用 `git init` 后就运行了 `git add ` 命令,开始跟踪当前目录下的文件。(((git commands, init)))(((git commands, add))) `git add` 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。 ==== 暂存已修改的文件 @@ -228,10 +235,7 @@ $ cat .gitignore * 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(`!`)取反。 所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 -星号(`\*`)匹配零个或多个任意字符;`[abc]` 匹配任何一个列在方括号中的字符 -(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); -问号(`?`)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, -表示所有在这两个字符范围内的都可以匹配(比如 `[0-9]` 表示匹配所有 0 到 9 的数字)。 +星号(`\*`)匹配零个或多个任意字符;`[abc]` 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(`?`)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 `[0-9]` 表示匹配所有 0 到 9 的数字)。 使用两个星号(`\**`)表示匹配任意中间目录,比如 `a/**/z` 可以匹配 `a/z` 、 `a/b/z` 或 `a/b/c/z` 等。 我们再看一个 `.gitignore` 文件的例子: @@ -259,15 +263,15 @@ doc/**/*.pdf [TIP] ==== -GitHub 有一个十分详细的针对数十种项目及语言的 `.gitignore` 文件列表, -你可以在 https://github.com/github/gitignore[] 找到它。 +GitHub 有一个十分详细的针对数十种项目及语言的 `.gitignore` 文件列表,你可以在 https://github.com/github/gitignore[^] 找到它。 ==== [NOTE] ==== 在最简单的情况下,一个仓库可能只根目录下有一个 `.gitignore` 文件,它递归地应用到整个仓库中。 然而,子目录下也可以有额外的 `.gitignore` 文件。子目录中的 `.gitignore` 文件中的规则只作用于它所在的目录中。 -(Linux 内核的源码库拥有 206 个 `.gitignore` 文件。) +这些嵌套的 `.gitignore` 文件中的规则仅适用于其所在目录下的文件。 +Linux 内核的源码库拥有 206 个 `.gitignore` 文件。 多个 `.gitignore` 文件的具体细节超出了本书的范围,更多详情见 `man gitignore` 。 ==== @@ -278,8 +282,7 @@ GitHub 有一个十分详细的针对数十种项目及语言的 `.gitignore` 如果 `git status` 命令的输出对于你来说过于简略,而你想知道具体修改了什么地方,可以用 `git diff` 命令。(((git commands, diff))) 稍后我们会详细介绍 `git diff`,你通常可能会用它来回答这两个问题:当前做的哪些更新尚未暂存? 有哪些更新已暂存并准备好下次提交? -虽然 `git status` 已经通过在相应栏下列出文件名的方式回答了这个问题,但 `git diff` -能通过文件补丁的格式更加具体地显示哪些行发生了改变。 +虽然 `git status` 已经通过在相应栏下列出文件名的方式回答了这个问题,但 `git diff` 能通过文件补丁的格式更加具体地显示哪些行发生了改变。 假如再次修改 README 文件后暂存,然后编辑 `CONTRIBUTING.md` 文件后先不暂存, 运行 `status` 命令将会看到: @@ -415,8 +418,7 @@ index 8ebb991..643e24f 100644 ==== 提交更新 现在的暂存区已经准备就绪,可以提交了。 -在此之前,请务必确认还有什么已修改或新建的文件还没有 `git add` 过, -否则提交的时候不会记录这些尚未暂存的变化。 +在此之前,请务必确认还有什么已修改或新建的文件还没有 `git add` 过,否则提交的时候不会记录这些尚未暂存的变化。 这些已修改但未暂存的文件只会保留在本地磁盘。 所以,每次准备提交前,先用 `git status` 看下,你所需要的文件是不是都已暂存起来了,(((git commands, status))) 然后再运行提交命令 `git commit`:(((git commands, commit))) @@ -430,10 +432,7 @@ $ git commit [NOTE] ==== -启动的编辑器是通过 Shell 的环境变量 `EDITOR` 指定的,一般为 vim 或 emacs。 -当然也可以按照 <> 介绍的方式, -使用 `git config --global core.editor` 命令设置你喜欢的编辑器。 -(((editor, changing default)))(((git commands, config))) +启动的编辑器是通过 Shell 的环境变量 `EDITOR` 指定的,一般为 vim 或 emacs,当然也可以按照 <> 介绍的方式,使用 `git config --global core.editor` 命令设置你喜欢的编辑器。(((editor, changing default)))(((git commands, config))) ==== 编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示): @@ -519,8 +518,7 @@ $ git commit -a -m 'added new benchmarks' 要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。 可以用 `git rm` 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。 -如果只是简单地从工作目录中手工删除文件,运行 `git status` 时就会在 -“Changes not staged for commit” 部分(也就是 _未暂存清单_)看到: +如果只是简单地从工作目录中手工删除文件,运行 `git status` 时就会在 “Changes not staged for commit” 部分(也就是 _未暂存清单_)看到: [source,console] ---- @@ -566,14 +564,15 @@ Changes to be committed: $ git rm --cached README ---- -`git rm` 命令后面可以列出文件或者目录的名字,也可以使用 `glob` 模式。比如: +`git rm` 命令后面可以列出文件或者目录的名字,也可以使用 `glob` 模式。 +比如: [source,console] ---- $ git rm log/\*.log ---- -注意到星号 `*` 之前的反斜杠 `\`, +注意到星号 `*` 之前的反斜杠 `\`。 因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。 此命令删除 `log/` 目录下扩展名为 `.log` 的所有文件。 类似的比如: @@ -627,5 +626,4 @@ $ git add README 如此分开操作,Git 也会意识到这是一次重命名,所以不管何种方式结果都一样。 两者唯一的区别在于,`git mv` 是一条命令而非三条命令,直接使用 `git mv` 方便得多。 -不过在使用其他工具重命名文件时,记得在提交前 `git rm` 删除旧文件名,再 `git add` -添加新文件名。 +不过在使用其他工具重命名文件时,记得在提交前 `git rm` 删除旧文件名,再 `git add` 添加新文件名。 diff --git a/book/02-git-basics/sections/remotes.asc b/book/02-git-basics/sections/remotes.asc index 0c0d7495..65b8cfec 100644 --- a/book/02-git-basics/sections/remotes.asc +++ b/book/02-git-basics/sections/remotes.asc @@ -64,7 +64,8 @@ origin git@github.com:mojombo/grit.git (fetch) origin git@github.com:mojombo/grit.git (push) ---- -这表示我们能非常方便地拉取其它用户的贡献。我们还可以拥有向他们推送的权限,这里暂不详述。 +这表示我们能非常方便地拉取其它用户的贡献。 +我们还可以拥有向他们推送的权限,这里暂不详述。 注意这些远程仓库使用了不同的协议。我们将会在 <> 中了解关于它们的更多信息。 @@ -101,9 +102,8 @@ From https://github.com/paulboone/ticgit * [new branch] ticgit -> pb/ticgit ---- -现在 Paul 的 master 分支可以在本地通过 `pb/master` 访问到——你可以将它合并到自己的某个分支中, -或者如果你想要查看它的话,可以检出一个指向该点的本地分支。 -(我们将会在 <> 中详细介绍什么是分支以及如何使用分支。) +现在 Paul 的 master 分支可以在本地通过 `pb/master` 访问到——你可以将它合并到自己的某个分支中,或者如果你想要查看它的话,可以检出一个指向该点的本地分支。 +我们将会在 <> 中详细介绍什么是分支以及如何使用分支。 [[_fetching_and_pulling]] ==== 从远程仓库中抓取与拉取 @@ -123,19 +123,28 @@ $ git fetch 必须注意 `git fetch` 命令只会将数据下载到你的本地仓库——它并不会自动合并或修改你当前的工作。 当准备好时你必须手动将其合并入你的工作。 -如果你的当前分支设置了跟踪远程分支(阅读下一节和 <> 了解更多信息), -那么可以用 `git pull` 命令来自动抓取后合并该远程分支到当前分支。(((git commands, pull))) -这或许是个更加简单舒服的工作流程。默认情况下,`git clone` 命令会自动设置本地 master -分支跟踪克隆的远程仓库的 `master` 分支(或其它名字的默认分支)。 +如果你的当前分支设置了跟踪远程分支(阅读下一节和 <> 了解更多信息),那么可以用 `git pull` 命令来自动抓取后合并该远程分支到当前分支。(((git commands, pull))) +这或许是个更加简单舒服的工作流程。默认情况下,`git clone` 命令会自动设置本地 `master` 分支跟踪克隆的远程仓库的 `master` 分支(或其它名字的默认分支)。 运行 `git pull` 通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。 +[NOTE] +==== +从 Git 2.27 版本开始,如果未设置 `pull.rebase` 变量,`git pull` 将发出警告。 +Git 会持续提醒你,直到你设置该变量为止。 + +如果你想让 Git 采用默认行为(如果可能则快进,否则创建一个合并提交): +`git config --global pull.rebase "false"` + +如果在拉取时想要变基: +`git config --global pull.rebase "true"` +==== + [[_pushing_remotes]] ==== 推送到远程仓库 当你想分享你的项目时,必须将其推送到上游。 这个命令很简单:`git push `。(((git commands, push))) -当你想要将 `master` 分支推送到 `origin` 服务器时(再次说明,克隆时通常会自动帮你设置好那两个名字), -那么运行这个命令就可以将你所做的备份到服务器: +当你想要将 `master` 分支推送到 `origin` 服务器时(再次说明,克隆时通常会自动帮你设置好那两个名字),那么运行这个命令就可以将你所做的备份到服务器: [source,console] ---- @@ -170,8 +179,7 @@ $ git remote show origin ---- 它同样会列出远程仓库的 URL 与跟踪分支的信息。 -这些信息非常有用,它告诉你正处于 `master` 分支,并且如果运行 `git pull`, -就会抓取所有的远程引用,然后将远程 `master` 分支合并到本地 `master` 分支。 +这些信息非常有用,它告诉你正处于 `master` 分支,并且如果运行 `git pull`,就会抓取所有的远程引用,然后将远程 `master` 分支合并到本地 `master` 分支。 它也会列出拉取到的所有远程引用。 这是一个经常遇到的简单例子。 @@ -202,8 +210,7 @@ $ git remote show origin ---- 这个命令列出了当你在特定的分支上执行 `git push` 会自动地推送到哪一个远程分支。 -它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了, -还有当你执行 `git pull` 时哪些本地分支可以与它跟踪的远程分支自动合并。 +它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了,还有当你执行 `git pull` 时哪些本地分支可以与它跟踪的远程分支自动合并。 ==== 远程仓库的重命名与移除 @@ -221,8 +228,7 @@ paul 值得注意的是这同样也会修改你所有远程跟踪的分支名字。 那些过去引用 `pb/master` 的现在会引用 `paul/master`。 -如果因为一些原因想要移除一个远程仓库——你已经从服务器上搬走了或不再想使用某一个特定的镜像了, -又或者某一个贡献者不再贡献了——可以使用 `git remote remove` 或 `git remote rm` : +如果因为一些原因想要移除一个远程仓库——你已经从服务器上搬走了或不再想使用某一个特定的镜像了,又或者某一个贡献者不再贡献了——可以使用 `git remote remove` 或 `git remote rm` : [source,console] ---- diff --git a/book/02-git-basics/sections/tagging.asc b/book/02-git-basics/sections/tagging.asc index bbfe08e9..57ea8152 100644 --- a/book/02-git-basics/sections/tagging.asc +++ b/book/02-git-basics/sections/tagging.asc @@ -42,8 +42,7 @@ v1.8.5.5 [NOTE] .按照通配符列出标签需要 `-l` 或 `--list` 选项 ==== -如果你只想要完整的标签列表,那么运行 `git tag` 就会默认假定你想要一个列表,它会直接给你列出来, -此时的 `-l` 或 `--list` 是可选的。 +如果你只想要完整的标签列表,那么运行 `git tag` 就会默认假定你想要一个列表,它会直接给你列出来,此时的 `-l` 或 `--list` 是可选的。 然而,如果你提供了一个匹配标签名的通配模式,那么 `-l` 或 `--list` 就是强制使用的。 ==== @@ -55,10 +54,8 @@ Git 支持两种标签:轻量标签(lightweight)与附注标签(annotate 轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。 而附注标签是存储在 Git 数据库中的一个完整对象, -它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, -此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 -通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, -或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。 +它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间,此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 +通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。 [[_annotated_tags]] ==== 附注标签 @@ -223,8 +220,8 @@ To git@github.com:schacon/simplegit.git [NOTE] .`git push` 推送两种标签 ==== -使用 `git push --tags` 推送标签并不会区分轻量标签和附注标签, -没有简单的选项能够让你只选择推送一种标签。 +`git push --tags` 会同时推送轻量标签和注释标签。 +目前尚无选项仅推送轻量标签,但如果你使用 `git push --follow-tags`,只会将注释标签推送到远程。 ==== ==== 删除标签 @@ -261,32 +258,36 @@ $ git push origin --delete ==== 检出标签 -如果你想查看某个标签所指向的文件版本,可以使用 `git checkout` 命令, -虽然这会使你的仓库处于“分离头指针(detached HEAD)”的状态——这个状态有些不好的副作用: +如果你想查看某个标签所指向的文件版本,可以使用 `git checkout` 命令,虽然这会使你的仓库处于“分离头指针(detached HEAD)”的状态——这个状态有些不好的副作用: [source,console] ---- -$ git checkout 2.0.0 -Note: checking out '2.0.0'. +$ git checkout v2.0.0 +Note: switching to 'v2.0.0'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may -do so (now or later) by using -b with the checkout command again. Example: +do so (now or later) by using -c with the switch command. Example: - git checkout -b + git switch -c + +Or undo this operation with: + + git switch - + +Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 99ada87... Merge pull request #89 from schacon/appendix-final $ git checkout 2.0-beta-0.1 Previous HEAD position was 99ada87... Merge pull request #89 from schacon/appendix-final -HEAD is now at df3f601... add atlas.json and cover image +HEAD is now at df3f601... Add atlas.json and cover image ---- -在“分离头指针”状态下,如果你做了某些更改然后提交它们,标签不会发生变化, -但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 +在“分离头指针”状态下,如果你做了某些更改然后提交它们,标签不会发生变化,但你的新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果你需要进行更改,比如你要修复旧版本中的错误,那么通常需要创建一个新分支: [source,console] @@ -295,5 +296,4 @@ $ git checkout -b version2 v2.0.0 Switched to a new branch 'version2' ---- -如果在这之后又进行了一次提交,`version2` 分支就会因为这个改动向前移动, -此时它就会和 `v2.0.0` 标签稍微有些不同,这时就要当心了。 +如果在这之后又进行了一次提交,`version2` 分支就会因为这个改动向前移动,此时它就会和 `v2.0.0` 标签稍微有些不同,这时就要当心了。 diff --git a/book/02-git-basics/sections/undoing.asc b/book/02-git-basics/sections/undoing.asc index 5c237b01..10c9c61a 100644 --- a/book/02-git-basics/sections/undoing.asc +++ b/book/02-git-basics/sections/undoing.asc @@ -15,8 +15,7 @@ $ git commit --amend ---- 这个命令会将暂存区中的文件提交。 -如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令), -那么快照会保持不变,而你所修改的只是提交信息。 +如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。 文本编辑器启动后,可以看到之前的提交信息。 编辑后保存会覆盖原来的提交信息。 @@ -34,11 +33,17 @@ $ git commit --amend [NOTE] ==== -当你在修补最后的提交时,与其说是修复旧提交,倒不如说是完全用一个 *新的提交* 替换旧的提交, -理解这一点非常重要。从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。 +当你在修补最后的提交时,与其说是修复旧提交,倒不如说是完全用一个 *新的提交* 替换旧的提交,理解这一点非常重要。 +从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。 -修补提交最明显的价值是可以稍微改进你最后的提交,而不会让“啊,忘了添加一个文件”或者 -“小修补,修正笔误”这种提交信息弄乱你的仓库历史。 +修补提交最明显的价值是可以稍微改进你最后的提交,而不会让“啊,忘了添加一个文件”或者“小修补,修正笔误”这种提交信息弄乱你的仓库历史。 +==== + +[NOTE] +==== +仅修改仍为本地且尚未推送到任何地方的提交。 +修改已推送的提交并强制推送分支,会为你的合作者带来问题。 +如需了解此类操作的具体影响以及在接收方时如何恢复,请参阅 <<_rebase_peril>>。 ==== [[_unstaging]] @@ -46,8 +51,8 @@ $ git commit --amend 接下来的两个小节演示如何操作暂存区和工作目录中已修改的文件。 这些命令在修改文件状态的同时,也会提示如何撤消操作。 -例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交, -但是却意外地输入 `git add *` 暂存了它们两个。如何只取消暂存两个中的一个呢? +例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输入 `git add *` 暂存了它们两个。 +如何只取消暂存两个中的一个呢? `git status` 命令提示了你: [source,console] @@ -136,10 +141,95 @@ Changes to be committed: 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。 ===== -如果你仍然想保留对那个文件做出的修改,但是现在仍然需要撤消,我们将会在 -<> 介绍保存进度与分支,这通常是更好的做法。 +如果你仍然想保留对那个文件做出的修改,但是现在仍然需要撤消,我们将会在 <> 介绍保存进度与分支,这通常是更好的做法。 记住,在 Git 中任何 *已提交* 的东西几乎总是可以恢复的。 -甚至那些被删除的分支中的提交或使用 `--amend` 选项覆盖的提交也可以恢复 -(阅读 <> 了解数据恢复)。 +甚至那些被删除的分支中的提交或使用 `--amend` 选项覆盖的提交也可以恢复(阅读 <> 了解数据恢复)。 然而,任何你未提交的东西丢失后很可能再也找不到了。 + +[[undoing_git_restore]] +==== 使用 git restore 撤销操作 + +Git 2.23.0 版本引入了一个新命令:`git restore`。 +它本质上是 `git reset` 的替代方案,而我们刚刚已经讨论过 `git reset`。 +从 Git 2.23.0 开始,Git 将在许多撤销操作中使用 `git restore` 而不是 `git reset`。 + +让我们回溯步骤,使用 `git restore` 而不是 `git reset` 来撤销操作。 + +===== 使用 git restore 恢复已暂存的文件 + +接下来的两个部分将演示如何使用 `git restore` 来处理你的暂存区和工作目录的更改。 +有趣的是,用于检查这两个区域状态的命令,还能提醒你如何撤销对它们的修改。 +例如,假设你修改了两个文件,并希望将它们作为两个独立的提交来记录,但不小心输入了 `git add *`,导致两个文件都被暂存了。 +那么,该如何取消其中一个文件的暂存呢? +`git status` 命令会提醒你: + +[source,console] +---- +$ git add * +$ git status +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: CONTRIBUTING.md + renamed: README.md -> README + +---- + +在“`Changes to be committed`”文本下方,写着使用 `git restore --staged ...` 来取消暂存。 +所以,让我们按照这个建议来取消暂存 `CONTRIBUTING.md` 文件: + +[source,console] +---- +$ git restore --staged CONTRIBUTING.md +$ git status +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + renamed: README.md -> README + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: CONTRIBUTING.md + +---- + +`CONTRIBUTING.md` 文件已被修改,但再次未暂存。 + +===== 使用 git restore 恢复修改后的文件 + +如果你意识到自己不想保留对 `CONTRIBUTING.md` 文件的修改,该怎么办? +如何轻松地撤销修改,将其恢复到你上次提交时(或最初克隆时,或以其他方式进入工作目录时)的状态? +幸运的是,`git status` 也能告诉你该如何操作。 +在上一个示例输出中,未暂存区域看起来是这样的: + +[source,console] +---- +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + modified: CONTRIBUTING.md + +---- + +它会明确告诉你如何丢弃你所做的更改。 +我们按照它的指示来操作: + +[source,console] +---- +$ git restore CONTRIBUTING.md +$ git status +On branch master +Changes to be committed: + (use "git restore --staged ..." to unstage) + renamed: README.md -> README + +---- + +[IMPORTANT] +===== +重要的是要明白,`git restore ` 是一个危险的命令。 +你对该文件所做的任何本地修改都会被清除——Git 只是用最后一次暂存或提交的版本替换了该文件。 +除非你绝对确定自己不需要保留这些未保存的本地修改,否则切勿使用此命令。 +===== diff --git a/book/02-git-basics/sections/viewing-history.asc b/book/02-git-basics/sections/viewing-history.asc index 75858794..8f0f7679 100644 --- a/book/02-git-basics/sections/viewing-history.asc +++ b/book/02-git-basics/sections/viewing-history.asc @@ -227,7 +227,7 @@ $ git log --pretty=format:"%h %s" --graph ==== 限制输出长度 除了定制输出格式的选项之外,`git log` 还有许多非常实用的限制输出长度的选项,也就是只输出一部分的提交。 -之前你已经看到过 `-2` 选项了,它只会显示最近的两条提交, +之前你已经看到过 `-2` 选项了,它只会显示最近的两条提交。 实际上,你可以使用类似 `-` 的选项,其中的 `n` 可以是任何整数,表示仅显示最近的 `n` 条提交。 不过实践中这个选项不是很常用,因为 Git 默认会将所有的输出传送到分页程序中,所以你一次只会看到一页的内容。 @@ -246,13 +246,10 @@ $ git log --since=2.weeks [NOTE] ==== -你可以指定多个 `--author` 和 `--grep` 搜索条件,这样会只输出匹配 *任意* -`--author` 模式和 *任意* `--grep` 模式的提交。然而,如果你添加了 `--all-match` 选项, -则只会输出匹配 *所有* `--grep` 模式的提交。 +你可以指定多个 `--author` 和 `--grep` 搜索条件,这样会只输出匹配 *任意* `--author` 模式和 *任意* `--grep` 模式的提交。然而,如果你添加了 `--all-match` 选项,则只会输出匹配 *所有* `--grep` 模式的提交。 ==== -另一个非常有用的过滤器是 `-S`(俗称“pickaxe”选项,取“用鹤嘴锄在土里捡石头”之意), -它接受一个字符串参数,并且只会显示那些添加或删除了该字符串的提交。 +另一个非常有用的过滤器是 `-S`(俗称“pickaxe”选项,取“用鹤嘴锄在土里捡石头”之意),它接受一个字符串参数,并且只会显示那些添加或删除了该字符串的提交。 假设你想找出添加或删除了对某一个特定函数的引用的提交,可以调用: [source,console] @@ -262,7 +259,12 @@ $ git log -S function_name 最后一个很实用的 `git log` 选项是路径(path), 如果只关心某些文件或者目录的历史提交,可以在 git log 选项的最后指定它们的路径。 -因为是放在最后位置上的选项,所以用两个短划线(--)隔开之前的选项和后面限定的路径名。 +因为是放在最后位置上的选项,所以用两个短划线(`--`)隔开之前的选项和后面限定的路径名。 + +[source,console] +---- +$ git log -- path/to/file +---- 在 <> 中列出了常用的选项 @@ -280,8 +282,7 @@ $ git log -S function_name | `-S` | 仅显示添加或删除内容匹配指定字符串的提交。 |================================ -来看一个实际的例子,如果要在 Git 源码库中查看 Junio Hamano 在 2008 年 10 月其间, -除了合并提交之外的哪一个提交修改了测试文件,可以使用下面的命令:(((log filtering))) +来看一个实际的例子,如果要在 Git 源码库中查看 Junio Hamano 在 2008 年 10 月其间,除了合并提交之外的哪一个提交修改了测试文件,可以使用下面的命令:(((log filtering))) [source,console] ---- diff --git a/book/03-git-branching/sections/basic-branching-and-merging.asc b/book/03-git-branching/sections/basic-branching-and-merging.asc index b77cd923..c5503b45 100644 --- a/book/03-git-branching/sections/basic-branching-and-merging.asc +++ b/book/03-git-branching/sections/basic-branching-and-merging.asc @@ -45,8 +45,7 @@ $ git checkout iss53 image::images/basic-branching-2.png[创建一个新分支指针。] 你继续在 #53 问题上工作,并且做了一些提交。 -在此过程中,`iss53` 分支在不断的向前推进,因为你已经检出到该分支 -(也就是说,你的 `HEAD` 指针指向了 `iss53` 分支) +在此过程中,`iss53` 分支在不断的向前推进,因为你已经检出到该分支(也就是说,你的 `HEAD` 指针指向了 `iss53` 分支) [source,console] ---- @@ -58,15 +57,12 @@ $ git commit -a -m 'added a new footer [issue 53]' image::images/basic-branching-3.png[`iss53` 分支随着工作的进展向前推进。] 现在你接到那个电话,有个紧急问题等待你来解决。 -有了 Git 的帮助,你不必把这个紧急问题和 `iss53` 的修改混在一起, -你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 +有了 Git 的帮助,你不必把这个紧急问题和 `iss53` 的修改混在一起,你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这个修改提交到线上分支。 你所要做的仅仅是切换回 `master` 分支。 -但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改, -它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 +但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 -有一些方法可以绕过这个问题(即,贮藏(stashing) 和 修补提交(commit amending)), -我们会在 <> 中看到关于这两个命令的介绍。 +有一些方法可以绕过这个问题(即,贮藏(stashing) 和 修补提交(commit amending)),我们会在 <> 中看到关于这两个命令的介绍。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 `master` 分支了: [source,console] @@ -109,10 +105,8 @@ Fast-forward ---- 在合并的时候,你应该注意到了“快进(fast-forward)”这个词。 -由于你想要合并的分支 `hotfix` 所指向的提交 `C4` 是你所在的提交 `C2` 的直接后继, -因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, -如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, -只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。 +由于你想要合并的分支 `hotfix` 所指向的提交 `C4` 是你所在的提交 `C2` 的直接后继,因此 Git 会直接将指针向前移动。 +换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。 现在,最新的修改已经在 `master` 分支所指向的提交快照中,你可以着手发布该修复了。 @@ -145,8 +139,7 @@ $ git commit -a -m 'finished the new footer [issue 53]' image::images/basic-branching-6.png[继续在 `iss53` 分支上的工作。] 你在 `hotfix` 分支上所做的工作并没有包含到 `iss53` 分支中。 -如果你需要拉取 `hotfix` 所做的修改,你可以使用 `git merge master` 命令将 `master` -分支合并入 `iss53` 分支,或者你也可以等到 `iss53` 分支完成其使命,再将其合并回 `master` 分支。 +如果你需要拉取 `hotfix` 所做的修改,你可以使用 `git merge master` 命令将 `master` 分支合并入 `iss53` 分支,或者你也可以等到 `iss53` 分支完成其使命,再将其合并回 `master` 分支。 [[_basic_merging]] ==== 分支的合并 @@ -272,8 +265,7 @@ Normal merge conflict for 'index.html': Hit return to start merge resolution tool (opendiff): ---- -如果你想使用除默认工具(在这里 Git 使用 `opendiff` 做为默认的合并工具,因为作者在 Mac 上运行该程序) -外的其他合并工具,你可以在 “下列工具中(one of the following tools)” 这句后面看到所有支持的合并工具。 +如果你想使用除默认工具(在这里 Git 使用 `opendiff` 做为默认的合并工具,因为作者在 Mac 上运行该程序)外的其他合并工具,你可以在 “下列工具中(one of the following tools)” 这句后面看到所有支持的合并工具。 然后输入你喜欢的工具名字就可以了。 [NOTE] @@ -282,7 +274,7 @@ Hit return to start merge resolution tool (opendiff): ==== 等你退出合并工具之后,Git 会询问刚才的合并是否成功。 -如果你回答是,Git 会暂存那些文件以表明冲突已解决: +如果你回答是,Git 会暂存那些文件以表明冲突已解决。 你可以再次运行 `git status` 来确认所有的合并冲突都已被解决: [source,console] @@ -323,5 +315,4 @@ Conflicts: # ---- -如果你觉得上述的信息不够充分,不能完全体现分支合并的过程,你可以修改上述信息, -添加一些细节给未来检视这个合并的读者一些帮助,告诉他们你是如何解决合并冲突的,以及理由是什么。 +如果你觉得上述的信息不够充分,不能完全体现分支合并的过程,你可以修改上述信息,添加一些细节给未来检视这个合并的读者一些帮助,告诉他们你是如何解决合并冲突的,以及理由是什么。 diff --git a/book/03-git-branching/sections/branch-management.asc b/book/03-git-branching/sections/branch-management.asc index d7c31ca7..85d542a1 100644 --- a/book/03-git-branching/sections/branch-management.asc +++ b/book/03-git-branching/sections/branch-management.asc @@ -62,11 +62,9 @@ If you are sure you want to delete it, run 'git branch -D testing'. [TIP] ==== -上面描述的选项 `--merged` 和 `--no-merged` 会在没有给定提交或分支名作为参数时, -分别列出已合并或未合并到 *当前* 分支的分支。 +上面描述的选项 `--merged` 和 `--no-merged` 会在没有给定提交或分支名作为参数时,分别列出已合并或未合并到 *当前* 分支的分支。 -你总是可以提供一个附加的参数来查看其它分支的合并状态而不必检出它们。 -例如,尚未合并到 `master` 分支的有哪些? +你总是可以提供一个附加的参数来查看其它分支的合并状态而不必检出它们。例如,尚未合并到 `master` 分支的有哪些? [source,console] ---- $ git checkout testing @@ -75,3 +73,111 @@ $ git branch --no-merged master featureB ---- ==== + +==== 修改分支名称 + +[CAUTION] +==== +不要重命名仍在其他合作者使用中的分支。 +在未阅读 <<_changing_master>> 部分之前,不要重命名像 master/main/mainline 这样的分支。 +==== + +假设你有一个名为 `bad-branch-name` 的分支,想要将其更改为 `corrected-branch-name`,并同时保留所有历史记录。 +你还需要将远程仓库(如 GitHub、GitLab 或其他服务器)上的分支名称也进行更改。 +该如何操作? + +使用 `git branch --move` 命令在本地重命名分支: + +[source, console] +---- +$ git branch --move bad-branch-name corrected-branch-name +---- + +这会将你的 `bad-branch-name` 替换为 `corrected-branch-name`,但目前这个更改仅限本地生效。 +要让其他人看到远程的修正分支,请执行推送操作: + +[source,console] +---- +$ git push --set-upstream origin corrected-branch-name +---- + +现在我们来简要回顾一下目前的状况: + +[source, console] +---- +$ git branch --all +* corrected-branch-name + main + remotes/origin/bad-branch-name + remotes/origin/corrected-branch-name + remotes/origin/main +---- + +请注意,您当前在远程分支 `corrected-branch-name` 上,该分支已存在。 +但名称错误的分支仍然保留在远程,您可以执行以下命令将其删除: + +[source,console] +---- +$ git push origin --delete bad-branch-name +---- + +现在,错误的分支名称已被完全替换为正确的分支名称。 + +[[_changing_master]] +===== 更改主分支名称 + +[WARNING] +==== +更改分支名称(如 master/main/mainline/default)将导致您的仓库所使用的集成(integrations)、服务(services)、辅助工具(helper utilities)以及构建/发布(build/release)脚本出现问题。 +在进行此操作前,请务必与您的合作者沟通确认。 +同时,请仔细搜索您的整个仓库,更新代码和脚本中所有对旧分支名称的引用。 +==== + +使用以下命令将本地的 `master` 分支重命名为 `main`: + +[source,console] +---- +$ git branch --move master main +---- + +本地的 `master` 分支已不存在,因为它已被重命名为 `main` 分支。 + +要让其他人看到新的 `main` 分支,你需要将其推送到远程仓库。 +这样重命名的分支就会在远程仓库中可用。 + +[source,console] +---- +$ git push --set-upstream origin main +---- + +现在我们得到的状态如下: + +[source,console] +---- +$ git branch --all +* main + remotes/origin/HEAD -> origin/master + remotes/origin/main + remotes/origin/master +---- + +你的本地 `master` 分支已经不存在,因为它已被 `main` 分支取代。 +`main` 分支存在于远程仓库中。 +但旧的 `master` 分支仍然保留在远程。 +其他合作者将继续以 `master` 分支作为他们工作的基础,直到你做出进一步的更改。 + +现在你还有几项任务需要完成,以完成过渡: + +* 任何依赖此项目的项目都需要更新其代码和/或配置。 +* 更新所有测试运行器的配置文件。 +* 调整构建(build)和发布(release)脚本。 +* 在你的仓库托管服务上重定向设置,例如仓库的默认分支、合并规则以及其他与分支名称相关的设置。 +* 更新文档中指向旧分支的引用。 +* 关闭或合并针对旧分支的任何拉取请求。 + +完成所有这些任务后,确认 `main` 分支与 `master` 分支的功能完全一致时,就可以删除 `master` 分支了: + +[source, console] +---- +$ git push origin --delete master +---- diff --git a/book/03-git-branching/sections/nutshell.asc b/book/03-git-branching/sections/nutshell.asc index 7c48f5f3..53cd6662 100644 --- a/book/03-git-branching/sections/nutshell.asc +++ b/book/03-git-branching/sections/nutshell.asc @@ -3,19 +3,13 @@ 为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。 -或许你还记得 <> 的内容, -Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 *快照* 。 +或许你还记得 <> 的内容,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 *快照* 。 -在进行提交操作时,Git 会保存一个提交对象(commit object)。 -知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 -但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。 -首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, -而由多个分支合并产生的提交对象有多个父对象, +在进行提交操作时,Git 会保存一个提交对象(commit object),其中包含指向你暂存内容快照的指针。 +该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针:首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象, 为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 -暂存操作会为每一个文件计算校验和(使用我们在 <> -中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 -(Git 使用 _blob_ 对象来保存它们),最终将校验和加入到暂存区域等待提交: +暂存操作会为每一个文件计算校验和(使用我们在 <> 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 _blob_ 对象来保存它们),最终将校验和加入到暂存区域等待提交: [source,console] ---- @@ -23,13 +17,10 @@ $ git add README test.rb LICENSE $ git commit -m 'The initial commit of my project' ---- -当使用 `git commit` 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, -然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, -它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 -如此一来,Git 就可以在需要的时候重现此次保存的快照。(((git commands, commit))) +当使用 `git commit` 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。 +随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。(((git commands, commit))) -现在,Git 仓库中有五个对象:三个 _blob_ 对象(保存着文件快照)、一个 *树* 对象 -(记录着目录结构和 blob 对象索引)以及一个 *提交* 对象(包含着指向前述树对象的指针和所有提交信息)。 +现在,Git 仓库中有五个对象:三个 _blob_ 对象(保存着文件快照)、一个 *树* 对象(记录着目录结构和 blob 对象索引)以及一个 *提交* 对象(包含着指向前述树对象的指针和所有提交信息)。 .首次提交对象及其树结构 image::images/commit-and-tree.png[首次提交对象及其树结构。] @@ -133,6 +124,18 @@ image::images/advance-testing.png[HEAD 分支随着提交操作自动向前移 $ git checkout master ---- +[NOTE] +.`git log` 不会始终显示 *所有* 分支 +==== +如果你现在运行 `git log`,可能会想知道刚刚创建的“testing”分支去了哪里,因为它不会出现在输出中。 + +分支并没有消失;Git 只是不知道你对这个分支感兴趣,它正在尝试显示它认为你可能感兴趣的版本。 +换句话说,默认情况下,`git log` 会只显示你检出的分支下方的提交历史记录。 + +要显示指定分支的提交历史,必须明确指定该分支:`git log testing`。 +要显示所有分支,请在 `git log` 命令中添加 `--all`。 +==== + .检出时 HEAD 随之移动 image::images/checkout-master.png[检出时 HEAD 随之移动。] @@ -185,8 +188,7 @@ $ git log --oneline --decorate --graph --all 这与过去大多数版本控制系统形成了鲜明的对比,它们在创建分支时,将所有的项目文件都复制一遍,并保存到一个特定的目录。 完成这样繁琐的过程通常需要好几秒钟,有时甚至需要好几分钟。所需时间的长短,完全取决于项目的规模。 -而在 Git 中,任何规模的项目都能在瞬间创建新分支。 -同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(译注:即共同祖先)也是同样的简单和高效。 +而在 Git 中,任何规模的项目都能在瞬间创建新分支。同时,由于每次提交都会记录父对象,所以寻找恰当的合并基础(译注:即共同祖先)也是同样的简单和高效。 这些高效的特性使得 Git 鼓励开发人员频繁地创建和使用分支。 接下来,让我们看看你为什么应该这样做。 @@ -196,3 +198,13 @@ $ git log --oneline --decorate --graph --all ==== 通常我们会在创建一个新分支后立即切换过去,这可以用 `git checkout -b ` 一条命令搞定。 ==== + +[NOTE] +==== +从 Git 2.23 版本开始,您可以使用 `git switch` 代替 `git checkout` 来完成以下操作: + +- 切换到现有分支:`git switch testing-branch`。 +- 创建新分支并切换到该分支:`git switch -c new-branch`。 + 其中 `-c` 表示 create(创建),你也可以使用完整标志:`--create`。 +- 返回到之前检出的分支:`git switch -`。 +==== diff --git a/book/03-git-branching/sections/rebasing.asc b/book/03-git-branching/sections/rebasing.asc index 34a93e5d..86be0ec2 100644 --- a/book/03-git-branching/sections/rebasing.asc +++ b/book/03-git-branching/sections/rebasing.asc @@ -33,10 +33,7 @@ First, rewinding head to replay your work on top of it... Applying: added staged command ---- -它的原理是首先找到这两个分支(即当前分支 `experiment`、变基操作的目标基底分支 `master`) -的最近共同祖先 `C2`,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, -然后将当前分支指向目标基底 `C3`, 最后以此将之前另存为临时文件的修改依序应用。 -(译注:写明了 commit id,以便理解,下同) +它的原理是首先找到这两个分支(即当前分支 `experiment`、变基操作的目标基底分支 `master`)的最近共同祖先 `C2`,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 `C3`, 最后以此将之前另存为临时文件的修改依序应用。(译注:写明了 commit id,以便理解,下同) .将 `C4` 中的修改变基到 `C3` 上 image::images/basic-rebase-3.png[将 `C4` 中的修改变基到 `C3` 上。] @@ -54,8 +51,7 @@ image::images/basic-rebase-4.png[`master` 分支的快进合并。] 此时,`C4'` 指向的快照就和 <> 中 `C5` 指向的快照一模一样了。 这两种整合方法的最终结果没有任何区别,但是变基使得提交历史更加整洁。 -你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, -但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。 +你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。 一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 `origin/master` 上,然后再向主项目提交修改。 @@ -76,18 +72,16 @@ image::images/basic-rebase-4.png[`master` 分支的快进合并。] .从一个主题分支里再分出一个主题分支的提交历史 image::images/interesting-rebase-1.png[从一个主题分支里再分出一个主题分支的提交历史。] -假设你希望将 `client` 中的修改合并到主分支并发布,但暂时并不想合并 `server` 中的修改, -因为它们还需要经过更全面的测试。这时,你就可以使用 `git rebase` 命令的 `--onto` 选项, -选中在 `client` 分支里但不在 `server` 分支里的修改(即 `C8` 和 `C9`),将它们在 `master` 分支上重放: +假设你希望将 `client` 中的修改合并到主分支并发布,但暂时并不想合并 `server` 中的修改,因为它们还需要经过更全面的测试。 +这时,你就可以使用 `git rebase` 命令的 `--onto` 选项,选中在 `client` 分支里但不在 `server` 分支里的修改(即 `C8` 和 `C9`),将它们在 `master` 分支上重放: [source,console] ---- $ git rebase --onto master server client ---- -以上命令的意思是:“取出 `client` 分支,找出它从 `server` 分支分歧之后的补丁, -然后把这些补丁在 `master` 分支上重放一遍,让 `client` 看起来像直接基于 -`master` 修改一样”。这理解起来有一点复杂,不过效果非常酷。 +以上命令的意思是:“取出 `client` 分支,找出它从 `server` 分支分歧之后的补丁,然后把这些补丁在 `master` 分支上重放一遍,让 `client` 看起来像直接基于 `master` 修改一样”。 +这理解起来有一点复杂,不过效果非常酷。 .截取主题分支上的另一个主题分支,然后变基到其他分支 image::images/interesting-rebase-2.png[截取主题分支上的另一个主题分支,然后变基到其他分支。] @@ -105,9 +99,7 @@ $ git merge client image::images/interesting-rebase-3.png[快进合并 `master` 分支,使之包含来自 `client` 分支的修改。] 接下来你决定将 `server` 分支中的修改也整合进来。 -使用 `git rebase ` 命令可以直接将主题分支 -(即本例中的 `server`)变基到目标分支(即 `master`)上。 -这样做能省去你先切换到 `server` 分支,再对其执行变基命令的多个步骤。 +使用 `git rebase ` 命令可以直接将主题分支(即本例中的 `server`)变基到目标分支(即 `master`)上。这样做能省去你先切换到 `server` 分支,再对其执行变基命令的多个步骤。 [source,console] ---- @@ -128,8 +120,7 @@ $ git checkout master $ git merge server ---- -至此,`client` 和 `server` 分支中的修改都已经整合到主分支里了, -你可以删除这两个分支,最终提交历史会变成图 <> 中的样子: +至此,`client` 和 `server` 分支中的修改都已经整合到主分支里了,你可以删除这两个分支,最终提交历史会变成图 <> 中的样子: [source,console] ---- @@ -192,7 +183,8 @@ image::images/perils-of-rebasing-4.png[你将相同的内容又合并了一次 如果你 *真的* 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。 -实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch-id”。 +实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和—— +即 “patch-id”。 如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。 @@ -219,8 +211,7 @@ image::images/perils-of-rebasing-5.png[在一个被变基然后强制推送的 如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 -如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, -那你就有大麻烦了,你的同事也会因此鄙视你。 +如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交,那你就有大麻烦了,你的同事也会因此鄙视你。 如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 `git pull --rebase` 命令,这样尽管不能避免伤痛,但能有所缓解。 @@ -238,11 +229,12 @@ image::images/perils-of-rebasing-5.png[在一个被变基然后强制推送的 另一种观点则正好相反,他们认为提交历史是 *项目过程中发生的事*。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 -持这一观点的人会使用 `rebase` 及 `filter-branch` 等工具来编写故事,怎么方便后来的读者就怎么写。 +在项目进行时,你可能需要记录下所有犯过的错误和走过的死胡同;但当你需要向外界展示成果时,或许更希望讲述一个从A到B的连贯故事。 +持这一观点的人会使用 `rebase` 及 `filter-branch` 等工具,在提交被合并到主分支之前重写他们的提交记录。 +利用 `rebase` 和 `filter-branch` 这类工具,以最适合未来读者理解的方式讲述故事。 现在,让我们回到之前的问题上来,到底合并还是变基好?希望你能明白,这并没有一个简单的答案。 Git 是一个非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同。 既然你已经分别学习了两者的用法,相信你能够根据实际情况作出明智的选择。 -总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, -从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。 +总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。 diff --git a/book/03-git-branching/sections/remote-branches.asc b/book/03-git-branching/sections/remote-branches.asc index d215bf3e..e18f7a3b 100644 --- a/book/03-git-branching/sections/remote-branches.asc +++ b/book/03-git-branching/sections/remote-branches.asc @@ -3,63 +3,55 @@ (((branches, remote)))(((references, remote))) 远程引用是对远程仓库的引用(指针),包括分支、标签等等。 -你可以通过 `git ls-remote ` 来显式地获得远程引用的完整列表, -或者通过 `git remote show ` 获得远程分支的更多信息。 +你可以通过 `git ls-remote ` 来显式地获得远程引用的完整列表,或者通过 `git remote show ` 获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。 -远程跟踪分支是远程分支状态的引用。它们是你无法移动的本地引用。一旦你进行了网络通信, -Git 就会为你移动它们以精确反映远程仓库的状态。请将它们看做书签, -这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。 +远程跟踪分支是远程分支状态的引用。 +它们是你无法移动的本地引用。一旦你进行了网络通信,Git 就会为你移动它们以精确反映远程仓库的状态。 +请将它们看做书签,这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。 它们以 `/` 的形式命名。 例如,如果你想要看你最后一次与远程仓库 `origin` 通信时 `master` 分支的状态,你可以查看 `origin/master` 分支。 -你与同事合作解决一个问题并且他们推送了一个 `iss53` 分支,你可能有自己的本地 `iss53` 分支, -然而在服务器上的分支会以 `origin/iss53` 来表示。 +你与同事合作解决一个问题并且他们推送了一个 `iss53` 分支,你可能有自己的本地 `iss53` 分支,然而在服务器上的分支会以 `origin/iss53` 来表示。 这可能有一点儿难以理解,让我们来看一个例子。 假设你的网络里有一个在 `git.ourcompany.com` 的 Git 服务器。 -如果你从这里克隆,Git 的 `clone` 命令会为你自动将其命名为 `origin`,拉取它的所有数据, -创建一个指向它的 `master` 分支的指针,并且在本地将其命名为 `origin/master`。 +如果你从这里克隆,Git 的 `clone` 命令会为你自动将其命名为 `origin`,拉取它的所有数据,创建一个指向它的 `master` 分支的指针,并且在本地将其命名为 `origin/master`。 Git 也会给你一个与 origin 的 `master` 分支在指向同一个地方的本地 `master` 分支,这样你就有工作的基础。 [NOTE] .“origin” 并无特殊含义 ==== 远程仓库名字 “origin” 与分支名字 “master” 一样,在 Git 中并没有任何特别的含义一样。 -同时 “master” 是当你运行 `git init` 时默认的起始分支名字,原因仅仅是它的广泛使用, -“origin” 是当你运行 `git clone` 时默认的远程仓库名字。 +同时 “master” 是当你运行 `git init` 时默认的起始分支名字,原因仅仅是它的广泛使用,“origin” 是当你运行 `git clone` 时默认的远程仓库名字。 如果你运行 `git clone -o booyah`,那么你默认的远程分支名字将会是 `booyah/master`。(((origin))) ==== .克隆之后的服务器与本地仓库 image::images/remote-branches-1.png[克隆之后的服务器与本地仓库。] -如果你在本地的 `master` 分支做了一些工作,在同一段时间内有其他人推送提交到 -`git.ourcompany.com` 并且更新了它的 `master` 分支,这就是说你们的提交历史已走向不同的方向。 +如果你在本地的 `master` 分支做了一些工作,在同一段时间内有其他人推送提交到 `git.ourcompany.com` 并且更新了它的 `master` 分支,这就是说你们的提交历史已走向不同的方向。 即便这样,只要你保持不与 `origin` 服务器连接(并拉取数据),你的 `origin/master` 指针就不会移动。 .本地与远程的工作可以分叉 image::images/remote-branches-2.png[本地与远程的工作可以分叉。] 如果要与给定的远程仓库同步数据,运行 `git fetch ` 命令(在本例中为 `git fetch origin`)。 -这个命令查找 “origin” 是哪一个服务器(在本例中,它是 `git.ourcompany.com`), -从中抓取本地没有的数据,并且更新本地数据库,移动 `origin/master` 指针到更新之后的位置。 +这个命令查找 “origin” 是哪一个服务器(在本例中,它是 `git.ourcompany.com`),从中抓取本地没有的数据,并且更新本地数据库,移动 `origin/master` 指针到更新之后的位置。 .`git fetch` 更新你的远程跟踪分支 image::images/remote-branches-3.png[`git fetch` 更新你的远程仓库引用。] 为了演示有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅服务于你的某个敏捷开发团队。 这个服务器位于 `git.team1.ourcompany.com`。 -你可以运行 `git remote add` 命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 -<> 中详细说明。 +你可以运行 `git remote add` 命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 <> 中详细说明。 将这个远程仓库命名为 `teamone`,将其作为完整 URL 的缩写。 .添加另一个远程仓库 image::images/remote-branches-4.png[添加另一个远程仓库。] 现在,可以运行 `git fetch teamone` 来抓取远程仓库 `teamone` 有而本地没有的数据。 -因为那台服务器上现有的数据是 `origin` 服务器上的一个子集, -所以 Git 并不会抓取数据而是会设置远程跟踪分支 `teamone/master` 指向 `teamone` 的 `master` 分支。 +因为那台服务器上现有的数据是 `origin` 服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程跟踪分支 `teamone/master` 指向 `teamone` 的 `master` 分支。 .远程跟踪分支 `teamone/master` image::images/remote-branches-5.png[远程跟踪分支 `teamone/master`。] @@ -88,14 +80,11 @@ To https://github.com/schacon/simplegit ---- 这里有些工作被简化了。 -Git 自动将 `serverfix` 分支名字展开为 `refs/heads/serverfix:refs/heads/serverfix`, -那意味着,“推送本地的 `serverfix` 分支来更新远程仓库上的 `serverfix` 分支。” -我们将会详细学习 <> 的 `refs/heads/` 部分, -但是现在可以先把它放在儿。你也可以运行 `git push origin serverfix:serverfix`, -它会做同样的事——也就是说“推送本地的 `serverfix` 分支,将其作为远程仓库的 `serverfix` 分支” +Git 自动将 `serverfix` 分支名字展开为 `refs/heads/serverfix:refs/heads/serverfix`,那意味着,“推送本地的 `serverfix` 分支来更新远程仓库上的 `serverfix` 分支。” +我们将会详细学习 <> 的 `refs/heads/` 部分,但是现在可以先把它放在儿。 +你也可以运行 `git push origin serverfix:serverfix`,它会做同样的事——也就是说“推送本地的 `serverfix` 分支,将其作为远程仓库的 `serverfix` 分支。” 可以通过这种格式来推送本地分支到一个命名不相同的远程分支。 -如果并不想让远程仓库上的分支叫做 `serverfix`,可以运行 `git push origin serverfix:awesomebranch` -来将本地的 `serverfix` 分支推送到远程仓库上的 `awesomebranch` 分支。 +如果并不想让远程仓库上的分支叫做 `serverfix`,可以运行 `git push origin serverfix:awesomebranch` 来将本地的 `serverfix` 分支推送到远程仓库上的 `awesomebranch` 分支。 [NOTE] .如何避免每次输入密码 @@ -178,8 +167,7 @@ Switched to a new branch 'sf' 现在,本地分支 `sf` 会自动从 `origin/serverfix` 拉取。 -设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, -你可以在任意时间使用 `-u` 或 `--set-upstream-to` 选项运行 `git branch` 来显式地设置。 +设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支,你可以在任意时间使用 `-u` 或 `--set-upstream-to` 选项运行 `git branch` 来显式地设置。 [source,console] ---- @@ -208,8 +196,7 @@ $ git branch -vv 这里可以看到 `iss53` 分支正在跟踪 `origin/iss53` 并且 “ahead” 是 2,意味着本地有两个提交还没有推送到服务器上。 也能看到 `master` 分支正在跟踪 `origin/master` 分支并且是最新的。 -接下来可以看到 `serverfix` 分支正在跟踪 `teamone` 服务器上的 `server-fix-good` 分支并且领先 3 落后 1, -意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。 +接下来可以看到 `serverfix` 分支正在跟踪 `teamone` 服务器上的 `server-fix-good` 分支并且领先 3 落后 1,意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。 最后看到 `testing` 分支并没有跟踪任何远程分支。 需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。 @@ -228,18 +215,13 @@ $ git fetch --all; git branch -vv 当 `git fetch` 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 `git pull` ,在大多数情况下它的含义是一个 `git fetch` 紧接着一个 `git merge` 命令。 -如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 -`clone` 或 `checkout` 命令为你创建的,`git pull` 都会查找当前分支所跟踪的服务器与分支, -从服务器上抓取数据然后尝试合并入那个远程分支。 - -由于 `git pull` 的魔法经常令人困惑所以通常单独显式地使用 `fetch` 与 `merge` 命令会更好一些。 +如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 `clone` 或 `checkout` 命令为你创建的,`git pull` 都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。 [[_delete_branches]] ==== 删除远程分支 (((branches, deleting remote))) -假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, -并且将其合并到了远程仓库的 `master` 分支(或任何其他稳定代码分支)。 +假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性,并且将其合并到了远程仓库的 `master` 分支(或任何其他稳定代码分支)。 可以运行带有 `--delete` 选项的 `git push` 命令来删除一个远程分支。 如果想要从服务器上删除 `serverfix` 分支,运行下面的命令: diff --git a/book/03-git-branching/sections/workflows.asc b/book/03-git-branching/sections/workflows.asc index 5023ac5c..707e8e39 100644 --- a/book/03-git-branching/sections/workflows.asc +++ b/book/03-git-branching/sections/workflows.asc @@ -1,8 +1,7 @@ === 分支开发工作流 现在你已经学会新建和合并分支,那么你可以或者应该用它来做些什么呢? -在本节,我们会介绍一些常见的利用分支进行开发的工作流程。而正是由于分支管理的便捷, -才衍生出这些典型的工作模式,你可以根据项目实际情况选择一种用用看。 +在本节,我们会介绍一些常见的利用分支进行开发的工作流程。而正是由于分支管理的便捷,才衍生出这些典型的工作模式,你可以根据项目实际情况选择一种用用看。 ==== 长期分支 @@ -51,16 +50,14 @@ image::images/lr-branches-2.png[趋于稳定分支的工作流(“silo”) .拥有多个主题分支的提交历史 image::images/topic-branches-1.png[拥有多个主题分支的提交历史。] -现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 `iss91v2` 分支中方案。 -另外,你将 `dumbidea` 分支拿给你的同事看过之后,结果发现这是个惊人之举。 +现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 `iss91v2` 分支中方案。另外,你将 `dumbidea` 分支拿给你的同事看过之后,结果发现这是个惊人之举。 这时你可以抛弃 `iss91` 分支(即丢弃 `C5` 和 `C6` 提交),然后把另外两个分支合并入主干分支。 最终你的提交历史看起来像下面这个样子: .合并了 `dumbidea` 和 `iss91v2` 分支之后的提交历史 image::images/topic-branches-2.png[合并了 `dumbidea` 和 `iss91v2` 分支之后的提交历史。] -我们将会在 <> 中向你揭示更多有关分支工作流的细节, -因此,请确保你阅读完那个章节之后,再来决定你的下个项目要使用什么样的分支策略(branching scheme)。 +我们将会在 <> 中向你揭示更多有关分支工作流的细节,因此,请确保你阅读完那个章节之后,再来决定你的下个项目要使用什么样的分支策略(branching scheme)。 请牢记,当你做这么多操作的时候,这些分支全部都存于本地。 当你新建和合并分支的时候,所有这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互。 diff --git a/book/04-git-server/sections/generating-ssh-key.asc b/book/04-git-server/sections/generating-ssh-key.asc index 2d04bda3..34536de1 100644 --- a/book/04-git-server/sections/generating-ssh-key.asc +++ b/book/04-git-server/sections/generating-ssh-key.asc @@ -19,8 +19,7 @@ config id_dsa.pub 我们需要寻找一对以 `id_dsa` 或 `id_rsa` 命名的文件,其中一个带有 `.pub` 扩展名。 `.pub` 文件是你的公钥,另一个则是与之对应的私钥。 -如果找不到这样的文件(或者根本没有 `.ssh` 目录),你可以通过运行 `ssh-keygen` 程序来创建它们。 -在 Linux/macOS 系统中,`ssh-keygen` 随 SSH 软件包提供;在 Windows 上,该程序包含于 MSysGit 软件包中。 +如果找不到这样的文件(或者根本没有 `.ssh` 目录),你可以通过运行 `ssh-keygen` 程序来创建它们。在 Linux/macOS 系统中,`ssh-keygen` 随 SSH 软件包提供,并且也包含在 Windows 版本的 Git 中: [source,console] ---- @@ -36,13 +35,11 @@ The key fingerprint is: d0:82:24:8e:d7:f1:bb:9b:33:53:96:93:49:da:9b:e3 schacon@mylaptop.local ---- -首先 `ssh-keygen` 会确认密钥的存储位置(默认是 `.ssh/id_rsa`),然后它会要求你输入两次密钥口令。 -如果你不想在使用密钥时输入口令,将其留空即可。 +首先 `ssh-keygen` 会确认密钥的存储位置(默认是 `.ssh/id_rsa`),然后它会要求你输入两次密钥口令。如果你不想在使用密钥时输入口令,将其留空即可。 然而,如果你使用了密码,那么请确保添加了 `-o` 选项,它会以比默认格式更能抗暴力破解的格式保存私钥。 你也可以用 `ssh-agent` 工具来避免每次都要输入密码。 -现在,进行了上述操作的用户需要将各自的公钥发送给任意一个 Git 服务器管理员 -(假设服务器正在使用基于公钥的 SSH 验证设置)。 +现在,进行了上述操作的用户需要将各自的公钥发送给任意一个 Git 服务器管理员(假设服务器正在使用基于公钥的 SSH 验证设置)。 他们所要做的就是复制各自的 `.pub` 文件内容,并将其通过邮件发送。 公钥看起来是这样的: @@ -58,4 +55,4 @@ NrRFi9wrf+M7Q== schacon@mylaptop.local ---- 关于在多种操作系统中生成 SSH 密钥的更深入教程,请参阅 GitHub 的 SSH 密钥指南 -https://docs.github.com/cn/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent[]。 +https://docs.github.com/cn/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent[^]。 diff --git a/book/04-git-server/sections/git-daemon.asc b/book/04-git-server/sections/git-daemon.asc index ebf506ed..498e71ae 100644 --- a/book/04-git-server/sections/git-daemon.asc +++ b/book/04-git-server/sections/git-daemon.asc @@ -6,8 +6,7 @@ 请注意,因为其不包含授权服务,任何通过该协议管理的内容将在其网络上公开。 如果运行在防火墙之外的服务器上,它应该只对那些公开的只读项目服务。 -如果运行在防火墙之内的服务器上,它可用于支撑大量参与人员或自动系统 -(用于持续集成或编译的主机)只读访问的项目,这样可以省去逐一配置 SSH 公钥的麻烦。 +如果运行在防火墙之内的服务器上,它可用于支撑大量参与人员或自动系统(用于持续集成或编译的主机)只读访问的项目,这样可以省去逐一配置 SSH 公钥的麻烦。 无论何时,该 Git 协议都是相对容易设定的。 通常,你只需要以守护进程的形式运行该命令:(((git commands, daemon))) @@ -17,9 +16,7 @@ $ git daemon --reuseaddr --base-path=/srv/git/ /srv/git/ ---- -`--reuseaddr` 选项允许服务器在无需等待旧连接超时的情况下重启,而 -`--base-path` 选项允许用户在未完全指定路径的条件下克隆项目, -结尾的路径将告诉 Git 守护进程从何处寻找仓库来导出。 +`--reuseaddr` 选项允许服务器在无需等待旧连接超时的情况下重启,而 `--base-path` 选项允许用户在未完全指定路径的条件下克隆项目,结尾的路径将告诉 Git 守护进程从何处寻找仓库来导出。 如果有防火墙正在运行,你需要开放端口 9418 的通信权限。 你可以通过许多方式将该进程以守护进程的方式运行,这主要取决于你所使用的操作系统。 @@ -53,8 +50,7 @@ WantedBy=multi-user.target 按需修改它并确保提供的用户在此系统上。此外,请确保 Git 二进制文件位于 `/usr/bin/git`,必要时可修改此路径。 -最后,你需要运行 `systemctl enable git-daemon` 以让它在系统启动时自动运行, -这样也能让它通过 `systemctl start git-daemon` 启动,通过 `systemctl stop git-daemon` 停止。 +最后,你需要运行 `systemctl enable git-daemon` 以让它在系统启动时自动运行,这样也能让它通过 `systemctl start git-daemon` 启动,通过 `systemctl stop git-daemon` 停止。 在其他系统中,你可以使用 `sysvinit` 系统中的 `xinetd` 脚本,或者另外的方式来实现——只要你能够将其命令守护进程化并实现监控。 diff --git a/book/04-git-server/sections/git-on-a-server.asc b/book/04-git-server/sections/git-on-a-server.asc index d44d62bf..1ce95b71 100644 --- a/book/04-git-server/sections/git-on-a-server.asc +++ b/book/04-git-server/sections/git-on-a-server.asc @@ -37,8 +37,7 @@ $ cp -Rf my_project/.git my_project.git ==== 把裸仓库放到服务器上 既然你有了裸仓库的副本,剩下要做的就是把裸仓库放到服务器上并设置你的协议。 -假设一个域名为 `git.example.com` 的服务器已经架设好,并可以通过 SSH 连接, -你想把所有的 Git 仓库放在 `/srv/git` 目录下。 +假设一个域名为 `git.example.com` 的服务器已经架设好,并可以通过 SSH 连接,你想把所有的 Git 仓库放在 `/srv/git` 目录下。 假设服务器上存在 `/srv/git/` 目录,你可以通过以下命令复制你的裸仓库来创建一个新仓库: [source,console] @@ -55,8 +54,7 @@ $ git clone user@git.example.com:/srv/git/my_project.git 如果一个用户,通过使用 SSH 连接到一个服务器,并且其对 `/srv/git/my_project.git` 目录拥有可写权限,那么他将自动拥有推送权限。 -如果到该项目目录中运行 `git init` 命令,并加上 `--shared` 选项, -那么 Git 会自动修改该仓库目录的组权限为可写。 +如果到该项目目录中运行 `git init` 命令,并加上 `--shared` 选项,那么 Git 会自动修改该仓库目录的组权限为可写。 注意,运行此命令的工程中不会摧毁任何提交、引用等内容。(((git commands, init, bare))) [source,console] @@ -69,8 +67,7 @@ $ git init --bare --shared 由此可见,根据现有的 Git 仓库创建一个裸仓库,然后把它放上你和协作者都有 SSH 访问权的服务器是多么容易。 现在你们已经准备好在同一项目上展开合作了。 -值得注意的是,这的确是架设一个几个人拥有连接权的 Git 服务的全部—— -只要在服务器上加入可以用 SSH 登录的帐号,然后把裸仓库放在大家都有读写权限的地方。 +值得注意的是,这的确是架设一个几个人拥有连接权的 Git 服务的全部——只要在服务器上加入可以用 SSH 登录的帐号,然后把裸仓库放在大家都有读写权限的地方。 你已经准备好了一切,无需更多。 下面的几节中,你会了解如何扩展到更复杂的设定。 @@ -86,8 +83,7 @@ $ git init --bare --shared ===== SSH 连接 (((serving repositories, SSH))) -如果你有一台所有开发者都可以用 SSH 连接的服务器,架设你的第一个仓库就十分简单了, -因为你几乎什么都不用做(正如我们上一节所说的)。 +如果你有一台所有开发者都可以用 SSH 连接的服务器,架设你的第一个仓库就十分简单了,因为你几乎什么都不用做(正如我们上一节所说的)。 如果你想在你的仓库上设置更复杂的访问控制权限,只要使用服务器操作系统的普通的文件系统权限就行了。 如果需要团队里的每个人都对仓库有写权限,又不能给每个人在服务器上建立账户,那么提供 SSH 连接就是唯一的选择了。 @@ -97,8 +93,7 @@ $ git init --bare --shared 第一个就是给团队里的每个人创建账号,这种方法很直接但也很麻烦。 或许你不会想要为每个人运行一次 `adduser`(或者 `useradd`)并且设置临时密码。 -第二个办法是在主机上建立一个 'git' 账户,让每个需要写权限的人发送一个 SSH 公钥, -然后将其加入 git 账户的 `~/.ssh/authorized_keys` 文件。 +第二个办法是在主机上建立一个 'git' 账户,让每个需要写权限的人发送一个 SSH 公钥,然后将其加入 git 账户的 `~/.ssh/authorized_keys` 文件。 这样一来,所有人都将通过 'git' 账户访问主机。 这一点也不会影响提交的数据——访问主机用的身份不会影响提交对象的提交者信息。 diff --git a/book/04-git-server/sections/gitlab.asc b/book/04-git-server/sections/gitlab.asc index 2a253264..b398542a 100644 --- a/book/04-git-server/sections/gitlab.asc +++ b/book/04-git-server/sections/gitlab.asc @@ -10,27 +10,24 @@ GitLab 是一个数据库支持的 web 应用,所以相比于其他 git 服务器,它的安装过程涉及到更多的东西。 幸运的是,这个过程有非常详细的文档说明和支持。 +GitLab 强烈建议通过官方的 Omnibus GitLab 包在您的服务器上安装 GitLab。 -这里有一些可参考的方法帮你安装 GitLab 。 -为了更快速的启动和运行,你可以下载虚拟机镜像或者在 https://bitnami.com/stack/gitlab[] -上获取一键安装包,同时调整配置使之符合你特定的环境。(((bitnami))) -Bitnami 的一个优点在于它的登录界面(通过 alt+→ 键进入), -它会告诉你安装好的 GitLab 的 IP 地址以及默认的用户名和密码。 +其他安装选项如下: -[[bitnami]] -.Bitnami GitLab 虚拟机登录界面。 -image::images/bitnami.png[Bitnami GitLab 虚拟机登录界面。] +* 用于 Kubernetes 的 GitLab Helm 镜像。 +* 用于 Docker 的 GitLab 镜像包。 +* 源代码文件。 +* 云服务提供商,如 AWS、Google Cloud Platform、Azure、OpenShift 和 Digital Ocean。 -无论如何,跟着 GitLab 社区版的 readme 文件一步步来,你可以在这里找到它 https://gitlab.com/gitlab-org/gitlab-ce/tree/master[] 。 -在这里你将会在主菜单中找到安装 GitLab 的帮助,一个可以在 Digital Ocean 上运行的虚拟机,以及 RPM 和 DEB 包(都是测试版)。 -这里还有 “非官方” 的引导让 GitLab 运行在非标准的操作系统和数据库上,一个全手动的安装脚本,以及许多其他的话题。 +更多信息请参阅 https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/README.md[GitLab 社区版 (CE) 说明文档^]。 ==== 管理 -GitLab 的管理界面是通过网络进入的。 -将你的浏览器转到已经安装 GitLab 的 主机名或 IP 地址,然后以管理员身份登录即可。 -默认的用户名是 `admin@local.host`,默认的密码是 `5iveL!fe`(你会得到类似 请登录后尽快更换密码 的提示)。 -登录后,点击主栏上方靠右位置的 “Admin area” 图标进行管理。 +GitLab 的管理界面可通过网页访问。 +只需将浏览器指向 GitLab 安装的主机名或 IP 地址,然后以 `root` 用户登录即可。 +密码取决于您的安装类型,但默认情况下,Omnibus GitLab 会自动生成密码,并将其存储在 `/etc/gitlab/initial_root_password` 中,至少保留 24 小时。 +更多详细信息,请参阅相关文档。 +登录后,点击右上角菜单中的“Admin area”图标。 [[gitlab_menu]] .GitLab 主栏的 “Admin area” 图标。 @@ -38,9 +35,9 @@ image::images/gitlab-menu.png[GitLab 主栏的 “Admin area” 图标。] ===== 使用者 -GitLab 上的用户指的是对应协作者的帐号。 -用户帐号没有很多复杂的地方,主要是包含登录数据的用户信息集合。 -每一个用户账号都有一个 *命名空间* ,即该用户项目的逻辑集合。 +使用您 GitLab 服务器的每个人必须拥有一个用户账户。 +用户账户非常简单,主要包含与登录数据关联的个人信息。 +每一个用户账号都有一个 *命名空间* ,即该用户所属项目的逻辑分组。 如果一个叫 +jane+ 的用户拥有一个名称是 +project+ 的项目,那么这个项目的 url 会是 `http://server/jane/project` 。 [[gitlab_users]] @@ -48,8 +45,7 @@ GitLab 上的用户指的是对应协作者的帐号。 image::images/gitlab-users.png[.GitLab 用户管理界面。] 移除一个用户有两种方法。 -“屏蔽(Blocking)” 一个用户阻止他登录 GitLab 实例,但是该用户命名空间下的所有数据仍然会被保存, -并且仍可以通过该用户提交对应的登录邮箱链接回他的个人信息页。 +“屏蔽(Blocking)” 一个用户阻止他登录 GitLab 实例,但是该用户命名空间下的所有数据仍然会被保存,并且仍可以通过该用户提交对应的登录邮箱链接回他的个人信息页。 而另一方面,“销毁(Destroying)” 一个用户,会彻底的将他从数据库和文件系统中移除。 他命名空间下的所有项目和数据都会被删除,拥有的任何组也会被移除。 @@ -59,8 +55,7 @@ image::images/gitlab-users.png[.GitLab 用户管理界面。] ===== 组 一个 GitLab 的组是一些项目的集合,连同关于多少用户可以访问这些项目的数据。 -每一个组都有一个项目命名空间(与用户一样),所以如果一个叫 +training+ -的组拥有一个名称是 +materials+ 的项目,那么这个项目的 url 会是 `http://server/training/materials`。 +每一个组都有一个项目命名空间(与用户一样),所以如果一个叫 +training+ 的组拥有一个名称是 +materials+ 的项目,那么这个项目的 url 会是 `http://server/training/materials` 。 [[gitlab_groups]] .GitLab 组管理界面。 @@ -74,8 +69,7 @@ image::images/gitlab-groups.png[GitLab 组管理界面。] 一个 GitLab 的项目相当于 git 的版本库。 每一个项目都属于一个用户或者一个组的单个命名空间。 -如果这个项目属于一个用户,那么这个拥有者对所有可以获取这个项目的人拥有直接管理权; -如果这个项目属于一个组,那么该组中用户级别的权限也会起作用。 +如果这个项目属于一个用户,那么这个拥有者对所有可以获取这个项目的人拥有直接管理权;如果这个项目属于一个组,那么该组中用户级别的权限也会起作用。 每一个项目都有一个可视级别,控制着谁可以看到这个项目页面和仓库。 如果一个项目是 _私有_ 的,这个项目的拥有者必须明确授权从而使特定的用户可以访问。 @@ -119,10 +113,8 @@ web 用户界面提供了几个有用的获取版本库信息的网页。 ==== 一起工作 在一个 GitLab 项目上一起工作的最简单方法就是赋予协作者对 git 版本库的直接 push 权限。 -你可以通过项目设定的 “Members(成员)” 部分向一个项目添加协作者, -并且将这个新的协作者与一个访问级别关联(不同的访问级别在 <<_gitlab_groups_section>> 中已简单讨论)。 -通过赋予一个协作者 “Developer(开发者)” 或者更高的访问级别, -这个用户就可以毫无约束地直接向版本库或者向分支进行提交。 +你可以通过项目设定的 “Members(成员)” 部分向一个项目添加协作者,并且将这个新的协作者与一个访问级别关联(不同的访问级别在 <<_gitlab_groups_section>> 中已简单讨论)。 +通过赋予一个协作者 “Developer(开发者)” 或者更高的访问级别,这个用户就可以毫无约束地直接向版本库或者向分支进行提交。 另外一个让合作更解耦的方法就是使用合并请求。 它的优点在于让任何能够看到这个项目的协作者在被管控的情况下对这个项目作出贡献。 @@ -134,7 +126,5 @@ web 用户界面提供了几个有用的获取版本库信息的网页。 每一个合并请求都允许在提出改变的行进行讨论(它支持一个轻量级的代码审查),也允许对一个总体性话题进行讨论。 两者都可以被分配给用户,或者组织到 milestones(里程碑) 界面。 -这个部分主要聚焦于在 GitLab 中与 Git 相关的特性,但是 GitLab 作为一个成熟的系统, -它提供了许多其他产品来帮助你协同工作,例如项目 wiki 与系统维护工具。 -GitLab 的一个优点在于,服务器设置和运行以后,你将很少需要调整配置文件或通过 SSH 连接服务器。 -绝大多数的管理和日常使用都可以在浏览器界面中完成。 +这个部分主要聚焦于在 GitLab 中与 Git 相关的特性,但是 GitLab 作为一个成熟的系统,它提供了许多其他产品来帮助你协同工作,例如项目 wiki 与系统维护工具。 +GitLab 的一个优点在于,服务器设置和运行以后,你将很少需要调整配置文件或通过 SSH 连接服务器。绝大多数的管理和日常使用都可以在浏览器界面中完成。 diff --git a/book/04-git-server/sections/gitweb.asc b/book/04-git-server/sections/gitweb.asc index d2a0fc18..0de08c06 100644 --- a/book/04-git-server/sections/gitweb.asc +++ b/book/04-git-server/sections/gitweb.asc @@ -8,8 +8,7 @@ Git 提供了一个叫做 GitWeb 的 CGI 脚本来做这项工作。 .GitWeb 的网页用户界面 image::images/git-instaweb.png[GitWeb 的网页用户界面] -如果你想要查看 GitWeb 如何展示你的项目,并且在服务器上安装了轻量级 Web 服务器比如 `lighttpd` 或 `webrick`, -Git 提供了一个命令来让你启动一个临时的服务器。 +如果你想要查看 GitWeb 如何展示你的项目,并且在服务器上安装了轻量级 Web 服务器比如 `lighttpd` 或 `webrick`,Git 提供了一个命令来让你启动一个临时的服务器。 在 Linux 系统的电脑上,`lighttpd` 通常已经安装了,所以你只需要在项目目录里执行 `git instaweb` 命令即可。 如果你使用 Mac 系统, Mac OS X Leopard 系统已经预安装了 Ruby,所以 `webrick` 或许是你最好的选择。 如果不想使用 lighttpd 启动 `instaweb` 命令,你需要在执行时加入 `--httpd` 参数。(((git commands, instaweb))) diff --git a/book/04-git-server/sections/hosted.asc b/book/04-git-server/sections/hosted.asc index 97cda188..95646a10 100644 --- a/book/04-git-server/sections/hosted.asc +++ b/book/04-git-server/sections/hosted.asc @@ -5,7 +5,6 @@ 即使你在内部设立并且运行了自己的服务器,你仍然可以把你的开源代码托管在公共托管网站——这通常更有助于开源社区来发现和帮助你。 现在,有非常多的托管供你选择,每个选择都有不同的优缺点。 -欲查看最新列表,请浏览 Git 维基的 GitHosting 页面 https://git.wiki.kernel.org/index.php/GitHosting[] +欲查看最新列表,请浏览 Git 维基的 GitHosting 页面 https://archive.kernel.org/oldwiki/git.wiki.kernel.org/index.php/GitHosting.html[^]。 -我们会在 <> 详细讲解 GitHub,作为目前最大的 Git 托管平台, -你很可能需要与托管在 GitHub 上的项目进行交互,而且你也很可能并不想去设立你自己的 Git 服务器。 +我们会在 <> 详细讲解 GitHub,作为目前最大的 Git 托管平台,你很可能需要与托管在 GitHub 上的项目进行交互,而且你也很可能并不想去设立你自己的 Git 服务器。 diff --git a/book/04-git-server/sections/protocols.asc b/book/04-git-server/sections/protocols.asc index 56eb42c8..009567cb 100644 --- a/book/04-git-server/sections/protocols.asc +++ b/book/04-git-server/sections/protocols.asc @@ -29,9 +29,7 @@ $ git clone file:///srv/git/project.git 如果在 URL 开头明确的指定 `file://`,那么 Git 的行为会略有不同。 如果仅是指定路径,Git 会尝试使用硬链接(hard link)或直接复制所需要的文件。 如果指定 `file://`,Git 会触发平时用于网路传输资料的进程,那样传输效率会更低。 -指定 `file://` 的主要目的是取得一个没有外部参考(extraneous references) -或对象(object)的干净版本库副本——通常是在从其他版本控制系统导入后或一些类似情况需要这么做 -(关于维护任务可参见 <> )。 +指定 `file://` 的主要目的是取得一个没有外部参考(extraneous references)或对象(object)的干净版本库副本——通常是在从其他版本控制系统导入后或一些类似情况需要这么做(关于维护任务可参见 <> )。 在此我们将使用普通路径,因为这样通常更快。 要增加一个本地版本库到现有的 Git 项目,可以执行如下的命令: @@ -47,12 +45,11 @@ $ git remote add local_proj /srv/git/project.git 基于文件系统的版本库的优点是简单,并且直接使用了现有的文件权限和网络访问权限。 如果你的团队已经有共享文件系统,建立版本库会十分容易。 -只需要像设置其他共享目录一样,把一个裸版本库的副本放到大家都可以访问的路径,并设置好读/写的权限,就可以了, +只需要像设置其他共享目录一样,把一个裸版本库的副本放到大家都可以访问的路径,并设置好读/写的权限,就可以了。 我们会在 <> 讨论如何导出一个裸版本库。 这也是快速从别人的工作目录中拉取更新的方法。 -如果你和别人一起合作一个项目,他想让你从版本库中拉取更新时,运行类似 -`git pull /home/john/project` 的命令比推送到服务器再抓取回来简单多了。 +如果你和别人一起合作一个项目,他想让你从版本库中拉取更新时,运行类似 `git pull /home/john/project` 的命令比推送到服务器再抓取回来简单多了。 ===== 缺点 @@ -78,17 +75,14 @@ Git 1.6.6 版本引入了一种新的、更智能的协议,让 Git 可以像 ===== 智能 HTTP 协议 (((protocols, smart HTTP))) -智能 HTTP 的运行方式和 SSH 及 Git 协议类似,只是运行在标准的 HTTP/S 端口上并且可以使用各种 HTTP 验证机制, -这意味着使用起来会比 SSH 协议简单的多,比如可以使用 HTTP 协议的用户名/密码授权,免去设置 SSH 公钥。 +智能 HTTP 的运行方式和 SSH 及 Git 协议类似,只是运行在标准的 HTTP/S 端口上并且可以使用各种 HTTP 验证机制,这意味着使用起来会比 SSH 协议简单的多,比如可以使用 HTTP 协议的用户名/密码授权,免去设置 SSH 公钥。 -智能 HTTP 协议或许已经是最流行的使用 Git 的方式了,它既支持像 `git://` 协议一样设置匿名服务, -也可以像 SSH 协议一样提供传输时的授权和加密。 +智能 HTTP 协议或许已经是最流行的使用 Git 的方式了,它既支持像 `git://` 协议一样设置匿名服务,也可以像 SSH 协议一样提供传输时的授权和加密。 而且只用一个 URL 就可以都做到,省去了为不同的需求设置不同的 URL。 如果你要推送到一个需要授权的服务器上(一般来讲都需要),服务器会提示你输入用户名和密码。 从服务器获取数据时也一样。 -事实上,类似 GitHub 的服务,你在网页上看到的 URL(比如 https://github.com/schacon/simplegit[]), -和你在克隆、推送(如果你有权限)时使用的是一样的。 +事实上,类似 GitHub 的服务,你在网页上看到的 URL(比如 https://github.com/schacon/simplegit[^]),和你在克隆、推送(如果你有权限)时使用的是一样的。 ===== 哑(Dumb) HTTP 协议 @@ -96,8 +90,7 @@ Git 1.6.6 版本引入了一种新的、更智能的协议,让 Git 可以像 如果服务器没有提供智能 HTTP 协议的服务,Git 客户端会尝试使用更简单的“哑” HTTP 协议。 哑 HTTP 协议里 web 服务器仅把裸版本库当作普通文件来对待,提供文件服务。 哑 HTTP 协议的优美之处在于设置起来简单。 -基本上,只需要把一个裸版本库放在 HTTP 根目录,设置一个叫做 `post-update` 的挂钩就可以了 -(见 <>)。 +基本上,只需要把一个裸版本库放在 HTTP 根目录,设置一个叫做 `post-update` 的挂钩就可以了(见 <>)。 此时,只要能访问 web 服务器上你的版本库,就可以克隆你的版本库。 下面是设置从 HTTP 访问版本库的方法: @@ -119,11 +112,10 @@ Git 自带的 `post-update` 挂钩会默认执行合适的命令(`git update-s $ git clone https://example.com/gitproject.git ---- -这里我们用了 Apache 里设置了常用的路径 `/var/www/htdocs`,不过你可以使用任何静态 Web 服务器 -—— 只需要把裸版本库放到正确的目录下就可以。 +这里我们用了 Apache 里设置了常用的路径 `/var/www/htdocs`,不过你可以使用任何静态 Web 服务器 —— 只需要把裸版本库放到正确的目录下就可以。 Git 的数据是以基本的静态文件形式提供的(详情见 <>)。 -通常的,会在可以提供读/写的智能 HTTP 服务和简单的只读的哑 HTTP 服务之间选一个。 +通常的,会在可以提供读/写的智能 HTTP 服务和简单的只读的哑 HTTP 服务之间选一个。 极少会将二者混合提供服务。 ===== 优点 @@ -174,26 +166,23 @@ $ git clone [user@]server:project.git ===== 优势 用 SSH 协议的优势有很多。 -首先,SSH 架设相对简单 —— SSH 守护进程很常见,多数管理员都有使用经验,并且多数操作系统都包含了它及相关的管理工具。 +首先,SSH 架设相对简单 —— SSH 守护进程很常见,多数管理员都有使用经验,并且多数操作系统都包含了它及相关的管理工具。 其次,通过 SSH 访问是安全的 —— 所有传输数据都要经过授权和加密。 最后,与 HTTPS 协议、Git 协议及本地协议一样,SSH 协议很高效,在传输前也会尽量压缩数据。 ===== 缺点 SSH 协议的缺点在于它不支持匿名访问 Git 仓库。 -如果你使用 SSH,那么即便只是读取数据,使用者也 *必须* 通过 SSH 访问你的主机, -这使得 SSH 协议不利于开源的项目,毕竟人们可能只想把你的仓库克隆下来查看。 +如果你使用 SSH,那么即便只是读取数据,使用者也 *必须* 通过 SSH 访问你的主机,这使得 SSH 协议不利于开源的项目,毕竟人们可能只想把你的仓库克隆下来查看。 如果你只在公司网络使用,SSH 协议可能是你唯一要用到的协议。 -如果你要同时提供匿名只读访问和 SSH 协议,那么你除了为自己推送架设 SSH 服务以外, -还得架设一个可以让其他人访问的服务。 +如果你要同时提供匿名只读访问和 SSH 协议,那么你除了为自己推送架设 SSH 服务以外,还得架设一个可以让其他人访问的服务。 ==== Git 协议 (((protocols, git))) 最后是 Git 协议。 这是包含在 Git 里的一个特殊的守护进程;它监听在一个特定的端口(9418),类似于 SSH 服务,但是访问无需任何授权。 -要让版本库支持 Git 协议,需要先创建一个 `git-daemon-export-ok` 文件 —— -它是 Git 协议守护进程为这个版本库提供服务的必要条件 —— 但是除此之外没有任何安全措施。 +要让版本库支持 Git 协议,需要先创建一个 `git-daemon-export-ok` 文件 —— 它是 Git 协议守护进程为这个版本库提供服务的必要条件 —— 但是除此之外没有任何安全措施。 要么谁都可以克隆这个版本库,要么谁也不能。 这意味着,通常不能通过 Git 协议推送。 由于没有授权机制,一旦你开放推送操作,意味着网络上知道这个项目 URL 的人都可以向项目推送数据。 @@ -207,10 +196,15 @@ SSH 协议的缺点在于它不支持匿名访问 Git 仓库。 ===== 缺点 -Git 协议缺点是缺乏授权机制。 -把 Git 协议作为访问项目版本库的唯一手段是不可取的。 -一般的做法里,会同时提供 SSH 或者 HTTPS 协议的访问服务,只让少数几个开发者有推送(写)权限,其他人通过 `git://` 访问只有读权限。 -Git 协议也许也是最难架设的。 +由于缺少 TLS 或其他加密机制,通过 `git://` 进行克隆可能导致任意代码执行漏洞,因此除非你非常清楚自己在做什么,否则应避免使用。 +* 如果你运行 `git clone git://example.com/project.git`,攻击者(例如控制你的路由器)可以修改你刚刚克隆的仓库,并在其中插入恶意代码。 +如果你随后编译或运行刚克隆的代码,就会执行这些恶意代码。 +因此,应避免运行 `git clone http://example.com/project.git` 。 +* 运行 `git clone https://example.com/project.git` 不会遇到同样的问题(除非攻击者能为 example.com 提供 TLS 证书)。 +而运行 `git clone git@example.com:project.git` 只有在你接受错误的 SSH 密钥指纹时才会出现此问题。 + +它也没有身份验证功能,也就是说任何人都可以克隆该仓库(尽管这通常正是你想要的)。 +此外,它可能是最难设置的协议。 它要求有自己的守护进程,这就要配置 `xinetd`、`systemd` 或者其他的程序,这些工作并不简单。 它还要求防火墙开放 9418 端口,但是企业防火墙一般不会开放这个非标准端口。 而大型的企业防火墙通常会封锁这个端口。 diff --git a/book/04-git-server/sections/setting-up-server.asc b/book/04-git-server/sections/setting-up-server.asc index 36718d5c..bc2051d8 100644 --- a/book/04-git-server/sections/setting-up-server.asc +++ b/book/04-git-server/sections/setting-up-server.asc @@ -4,7 +4,6 @@ 我们来看看如何配置服务器端的 SSH 访问。 本例中,我们将使用 `authorized_keys` 方法来对用户进行认证。 同时我们假设你使用的操作系统是标准的 Linux 发行版,比如 Ubuntu。 -首先,创建一个操作系统用户 `git`,并为其建立一个 `.ssh` 目录。 [NOTE] ==== @@ -57,12 +56,10 @@ $ git init --bare Initialized empty Git repository in /srv/git/project.git/ ---- -接着,John、Josie 或者 Jessica 中的任意一人可以将他们项目的最初版本推送到这个仓库中, -他只需将此仓库设置为项目的远程仓库并向其推送分支。 +接着,John、Josie 或者 Jessica 中的任意一人可以将他们项目的最初版本推送到这个仓库中,他只需将此仓库设置为项目的远程仓库并向其推送分支。 请注意,每添加一个新项目,都需要有人登录服务器取得 shell,并创建一个裸仓库。 我们假定这个设置了 `git` 用户和 Git 仓库的服务器使用 `gitserver` 作为主机名。 -同时,假设该服务器运行在内网,并且你已在 DNS 配置中将 `gitserver` 指向此服务器。 -那么我们可以运行如下命令(假定 `myproject` 是已有项目且其中已包含文件): +同时,假设该服务器运行在内网,并且你已在 DNS 配置中将 `gitserver` 指向此服务器。那么我们可以运行如下命令(假定 `myproject` 是已有项目且其中已包含文件): [source,console] ---- @@ -92,16 +89,15 @@ $ git push origin master 如果你想对此加以限制,则需要修改 `/etc/passwd` 文件中(`git` 用户所对应)的 shell 值。 借助一个名为 `git-shell` 的受限 shell 工具,你可以方便地将用户 `git` 的活动限制在与 Git 相关的范围内。 -该工具随 Git 软件包一同提供。如果将 `git-shell` 设置为用户 `git` 的登录 shell(login shell), -那么该用户便不能获得此服务器的普通 shell 访问权限。 +该工具随 Git 软件包一同提供。如果将 `git-shell` 设置为用户 `git` 的登录 shell(login shell),那么该用户便不能获得此服务器的普通 shell 访问权限。 若要使用 `git-shell`,需要用它替换掉 bash 或 csh,使其成为该用户的登录 shell。 为进行上述操作,首先你必须确保 `git-shell` 的完整路径名已存在于 `/etc/shells` 文件中: [source,console] ---- -$ cat /etc/shells # see if git-shell is already in there. If not... -$ which git-shell # make sure git-shell is installed on your system. -$ sudo -e /etc/shells # and add the path to git-shell from last command +$ cat /etc/shells # 检查是否已包含 git-shell。如果没有... +$ which git-shell # 确保你的系统上已安装 git-shell。 +$ sudo -e /etc/shells # 并将上一条命令中的 git-shell 路径添加进去 ---- 现在你可以使用 `chsh -s ` 命令修改任一系统用户的 shell: diff --git a/book/04-git-server/sections/smart-http.asc b/book/04-git-server/sections/smart-http.asc index efe02f84..50e31640 100644 --- a/book/04-git-server/sections/smart-http.asc +++ b/book/04-git-server/sections/smart-http.asc @@ -1,12 +1,10 @@ === Smart HTTP (((serving repositories, HTTP))) -我们一般通过 SSH 进行授权访问,通过 git:// 进行无授权访问,但是还有一种协议可以同时实现以上两种方式的访问。 +我们一般通过 SSH 进行授权访问,通过 `git://` 进行无授权访问,但是还有一种协议可以同时实现以上两种方式的访问。 设置 Smart HTTP 一般只需要在服务器上启用一个 Git 自带的名为 `git-http-backend` 的 CGI 脚本。(((git commands, "http-backend"))) -该 CGI 脚本将会读取由 `git fetch` 或 `git push` 命令向 HTTP URL 发送的请求路径和头部信息, -来判断该客户端是否支持 HTTP 通信(不低于 1.6.6 版本的客户端支持此特性)。 -如果 CGI 发现该客户端支持智能(Smart)模式,它将会以智能模式与它进行通信, -否则它将会回落到哑(Dumb)模式下(因此它可以对某些老的客户端实现向下兼容)。 +该 CGI 脚本将会读取由 `git fetch` 或 `git push` 命令向 HTTP URL 发送的请求路径和头部信息,来判断该客户端是否支持 HTTP 通信(不低于 1.6.6 版本的客户端支持此特性)。 +如果 CGI 发现该客户端支持智能(Smart)模式,它将会以智能模式与它进行通信,否则它将会回落到哑(Dumb)模式下(因此它可以对某些老的客户端实现向下兼容)。 在完成以上简单的安装步骤后, 我们将用 Apache 来作为 CGI 服务器。 @@ -18,10 +16,9 @@ $ sudo apt-get install apache2 apache2-utils $ a2enmod cgi alias env ---- -该操作将会启用 `mod_cgi`, `mod_alias` 和 `mod_env` 等 Apache 模块, 这些模块都是使该功能正常工作所必须的。 +该操作将会启用 `mod_cgi`, `mod_alias` 和 `mod_env` 等 Apache 模块,这些模块都是使该功能正常工作所必须的。 -你还需要将 `/srv/git` 的 Unix 用户组设置为 `www-data`,这样 Web 服务器才能读写该仓库, -因为运行 CGI 脚本的 Apache 实例默认会以该用户的权限运行: +你还需要将 `/srv/git` 的 Unix 用户组设置为 `www-data`,这样 Web 服务器才能读写该仓库,因为运行 CGI 脚本的 Apache 实例默认会以该用户的权限运行: [source,console] ---- @@ -71,6 +68,5 @@ $ htpasswd -c /srv/git/.htpasswd schacon [NOTE] ==== -欲了解更多的有关配置 Apache 授权访问的信息,请通过以下链接浏览 Apache 文档: -https://httpd.apache.org/docs/current/howto/auth.html[] +欲了解更多的有关配置 Apache 授权访问的信息,请通过以下链接浏览 Apache 文档: https://httpd.apache.org/docs/current/howto/auth.html[^]。 ==== diff --git a/book/05-distributed-git/sections/contributing.asc b/book/05-distributed-git/sections/contributing.asc index 5fff1616..95604130 100644 --- a/book/05-distributed-git/sections/contributing.asc +++ b/book/05-distributed-git/sections/contributing.asc @@ -60,7 +60,7 @@ image::images/git-diff-check.png[`git diff --check` 的输出。] 一般情况下,信息应当以少于 50 个字符(25个汉字)的单行开始且简要地描述变更,接着是一个空白行,再接着是一个更详细的解释。 Git 项目要求一个更详细的解释,包括做改动的动机和它的实现与之前行为的对比——这是一个值得遵循的好规则。 使用指令式的语气来编写提交信息,比如使用“Fix bug”而非“Fixed bug”或“Fixes bug”。 -这里是一份 https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[最初由 Tim Pope 写的模板]: +这里是一份 https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[最初由 Tim Pope 写的模板^]: [source,text] ---- @@ -69,9 +69,12 @@ Git 项目要求一个更详细的解释,包括做改动的动机和它的实 如果必要的话,加入更详细的解释文字。在大概 72 个字符的时候换行。 在某些情形下,第一行被当作一封电子邮件的标题,剩下的文本作为正文。 分隔摘要与正文的空行是必须的(除非你完全省略正文), -如果你将两者混在一起,那么在使用例如变基这样的工具时,它们会生成难以阅读的输出,让人困惑。 +如果你将两者混在一起, +那么在使用例如变基这样的工具时, +它们会生成难以阅读的输出,让人困惑。 -使用指令式的语气来编写提交信息:使用“Fix bug”而非“Fixed bug”或“Fixes bug”。 +使用指令式的语气来编写提交信息: +使用“Fix bug”而非“Fixed bug”或“Fixes bug”。 此约定与 git merge 和 git revert 命令生成提交说明相同。 空行接着更进一步的段落。 @@ -85,14 +88,12 @@ Git 项目要求一个更详细的解释,包括做改动的动机和它的实 ---- 如果你所有的提交信息都遵循此模版,那么对你和与你协作的其他开发者来说事情会变得非常容易。 -Git 项目有一个良好格式化的提交信息——尝试在那儿运行 `git log --no-merges` -来看看漂亮的格式化的项目提交历史像什么样。 +Git 项目有一个良好格式化的提交信息——尝试在那儿运行 `git log --no-merges` 来看看漂亮的格式化的项目提交历史像什么样。 [NOTE] .按我们说的去做,不要照着我们做的去做。 ==== -为简单起见,本书中很多例子的提交说明并没有遵循这样良好的格式, -我们只是对 `git commit` 使用了 `-m` 选项。 +为简单起见,本书中很多例子的提交说明并没有遵循这样良好的格式,我们只是对 `git commit` 使用了 `-m` 选项。 简而言之,按我们说的去做,不要照着我们做的去做。 ==== @@ -151,9 +152,9 @@ To jessica@githost:simplegit.git ---- 上方输出信息中最后一行显示的是推送操作执行完毕后返回的一条很有用的消息。 -消息的基本格式是 `.. fromref -> toref` , `oldref` 的含义是推送前所指向的引用, `newref` 的含义是推送后所指向的引用, `fromref` 是将要被推送的本地引用的名字, `toref` 是将要被更新的远程引用的名字。 +消息的基本格式是 `.. fromref -> toref` , `oldref` 的含义是推送前所指向的引用,`newref` 的含义是推送后所指向的引用,`fromref` 是将要被推送的本地引用的名字,`toref` 是将要被更新的远程引用的名字。 在后面的讨论中你还会看到类似的输出消息,所以对这条消息的含义有一些基础的了解将会帮助你理解仓库的诸多状态。 -想要了解更多细节请访问文档 https://git-scm.com/docs/git-push[git-push] 。 +想要了解更多细节请访问文档 https://git-scm.com/docs/git-push[git-push^]。 John 稍候也做了些改动,将它们提交到了本地仓库中,然后试着将它们推送到同一个服务器: @@ -201,8 +202,7 @@ Merge made by the 'recursive' strategy. .合并了 `origin/master` 之后 John 的仓库 image::images/small-team-2.png[合并了 `origin/master` 之后 John 的仓库。] -此时,John 可能想要测试新的代码,以确保 Jessica 的工作没有影响他自己的工作, -当一切正常后,他就能将新合并的工作推送到服务器了: +此时,John 可能想要测试新的代码,以确保 Jessica 的工作没有影响他自己的工作,当一切正常后,他就能将新合并的工作推送到服务器了: [source,console] ---- @@ -223,8 +223,7 @@ image::images/small-team-3.png[推送到 `origin` 服务器后 John 的历史。 .Jessica 的主题分支 image::images/small-team-4.png[Jessica 的主题分支。] -忽然,Jessica 发现 John 向服务器推送了一些新的工作,她想要看一下, -于是就抓取了所有服务器上的新内容: +忽然,Jessica 发现 John 向服务器推送了一些新的工作,她想要看一下,于是就抓取了所有服务器上的新内容: [source,console] ---- @@ -254,18 +253,15 @@ Date: Fri May 29 16:01:27 2009 -0700 remove invalid default value ---- -`issue54..origin/master` 语法是一个日志过滤器,要求 Git 只显示所有在后面分支 -(在本例中是 `origin/master`)但不在前面分支(在本例中是 `issue54`)的提交的列表。 +`issue54..origin/master` 语法是一个日志过滤器,要求 Git 只显示所有在后面分支(在本例中是 `origin/master`)但不在前面分支(在本例中是 `issue54`)的提交的列表。 我们将会在 <> 中详细介绍这个语法。 目前,我们可以从输出中看到有一个 John 生成的但是 Jessica 还没有合并的提交。 如果她合并 `origin/master`,那个未合并的提交将会修改她的本地工作。 -现在,Jessica 可以合并她的特性工作到她的 master 分支, -合并 John 的工作(`origin/master`)进入她的 `master` 分支,然后再次推送回服务器。 +现在,Jessica 可以合并她的特性工作到她的 master 分支,合并 John 的工作(`origin/master`)进入她的 `master` 分支,然后再次推送回服务器。 -首先(在已经提交了所有 `issue54` 主题分支上的工作后),为了整合所有这些工作, -她切换回她的 master 分支。 +首先(在已经提交了所有 `issue54` 主题分支上的工作后),为了整合所有这些工作,她切换回她的 master 分支: [source,console] ---- @@ -305,8 +301,7 @@ Merge made by the 'recursive' strategy. .合并了 John 的改动后 Jessica 的历史 image::images/small-team-6.png[合并了 John 的改动后 Jessica 的历史。] -现在 `origin/master` 是可以从 Jessica 的 `master` 分支到达的, -所以她应该可以成功地推送(假设同一时间 John 并没有更多推送): +现在 `origin/master` 是可以从 Jessica 的 `master` 分支到达的,所以她应该可以成功地推送(假设同一时间 John 并没有更多推送): [source,console] ---- @@ -323,8 +318,8 @@ image::images/small-team-7.png[推送所有的改动回服务器后 Jessica 的 这是一个最简单的工作流程。 你通常会在一个主题分支上工作一会儿,当它准备好整合时就合并到你的 `master` 分支。 -当想要共享工作时,如果有改动的话就抓取它然后合并到你自己的 `master` 分支, -之后推送到服务器上的 `master` 分支。通常顺序像这样: +当想要共享工作时,如果有改动的话就抓取它然后合并到你自己的 `master` 分支,之后推送到服务器上的 `master` 分支。 +通常顺序像这样: .一个简单的多人 Git 工作流程的通常事件顺序 image::images/small-team-flow.png[一个简单的多人 Git 工作流程的通常事件顺序。] @@ -335,10 +330,8 @@ image::images/small-team-flow.png[一个简单的多人 Git 工作流程的通 在接下来的场景中,你会看到大型私有团队中贡献者的角色。 你将学到如何在这种工作环境中工作,其中小组基于特性进行协作,而这些团队的贡献将会由其他人整合。 -让我们假设 John 与 Jessica 在一个特性(`featureA`)上工作, -同时 Jessica 与第三个开发者 Josie 在第二个特性(`featureB`)上工作。 -在本例中,公司使用了一种整合-管理者工作流程,独立小组的工作只能被特定的工程师整合, -主仓库的 `master` 分支只能被那些工程师更新。 +让我们假设 John 与 Jessica 在一个特性(`featureA`)上工作,同时 Jessica 与第三个开发者 Josie 在第二个特性(`featureB`)上工作。 +在本例中,公司使用了一种整合-管理者工作流程,独立小组的工作只能被特定的工程师整合,主仓库的 `master` 分支只能被那些工程师更新。 在这种情况下,所有的工作都是在基于团队的分支上完成的并且稍后会被整合者拉到一起。 因为 Jessica 在两个特性上工作,并且平行地与两个不同的开发者协作,让我们跟随她的工作流程。 @@ -398,8 +391,7 @@ $ git commit -am 'add ls-files' .Jessica 的初始提交历史 image::images/managed-team-1.png[Jessica 的初始提交历史。] -她准备好推送工作了,但是一封来自 Josie 的邮件告知一些初始的“featureB” -工作已经被推送到服务器的 `featureBee` 上了。 +她准备好推送工作了,但是一封来自 Josie 的邮件告知一些初始的“featureB”工作已经被推送到服务器的 `featureBee` 上了。 Jessica 在能够将她的工作推送到服务器前,需要将那些改动与她自己的合并。 她首先通过 `git fetch` 抓取了 Josie 的改动: @@ -473,8 +465,7 @@ Fast forward 1 files changed, 9 insertions(+), 1 deletions(-) ---- -最后,Jessica 可能想要对整个合并后的内容做一些小修改, -于是她将这些修改提交到了本地的 `featureA` 分支,接着将最终的结果推送回了服务器。 +最后,Jessica 可能想要对整个合并后的内容做一些小修改,于是她将这些修改提交到了本地的 `featureA` 分支,接着将最终的结果推送回了服务器。 [source,console] ---- @@ -531,8 +522,7 @@ $ git commit [NOTE] ==== -你可以用 `rebase -i` 将工作压缩成一个单独的提交,或者重排提交中的工作使补丁更容易被维护者审核—— -查看 <> 了解关于交互式变基的更多信息。 +你可以用 `rebase -i` 将工作压缩成一个单独的提交,或者重排提交中的工作使补丁更容易被维护者审核 —— 查看 <> 了解关于交互式变基的更多信息。 ==== 当你的分支工作完成后准备将其贡献回维护者,去原始项目中然后点击“Fork”按钮,创建一份自己的可写的项目派生仓库。 @@ -545,8 +535,7 @@ $ git remote add myfork 然后需要推送工作到上面。 相对于合并到主分支再推送上去,推送你正在工作的主题分支到仓库上更简单。 -原因是工作如果不被接受或者是被拣选的,就不必回退你的 master 分支 -(拣选操作 `cherry-pick` 详见 <>)。 +原因是工作如果不被接受或者是被拣选的,就不必回退你的 master 分支(拣选操作 `cherry-pick` 详见 <>)。 如果维护者合并、变基或拣选你的工作,不管怎样你最终会通过拉取他们的仓库找回来你的工作。 在任何情况下,你都可以使用下面的命令推送你的工作: @@ -558,12 +547,9 @@ $ git push -u myfork featureA (((git commands, request-pull))) 当工作已经被推送到你的派生仓库后,你需要通知原项目的维护者你有想要他们合并的工作。 -这通常被称作一个 *拉取请求(Pull Request)*,你通常可以通过网站生成它—— -GitHub 有它自己的 Pull Request 机制,我们将会在 <> -介绍——也可以运行 `git request-pull` 命令然后将随后的输出通过电子邮件手动发送给项目维护者。 +这通常被称作一个 *拉取请求(Pull Request)*,你通常可以通过网站生成它 —— GitHub 有它自己的 Pull Request 机制,我们将会在 <> 介绍 —— 也可以运行 `git request-pull` 命令然后将随后的输出通过电子邮件手动发送给项目维护者。 -`git request-pull` 命令接受一个要拉取主题分支的基础分支,以及它们要拉取的 Git 仓库的 URL, -产生一个请求拉取的所有修改的摘要。 +`git request-pull` 命令接受一个要拉取主题分支的基础分支,以及它们要拉取的 Git 仓库的 URL,产生一个请求拉取的所有修改的摘要。 例如,Jessica 想要发送给 John 一个拉取请求,她已经在刚刚推送的分支上做了两次提交。她可以运行这个: [source,console] @@ -640,10 +626,9 @@ $ git commit $ git push myfork featureBv2 ---- -`--squash` 选项接受被合并的分支上的所有工作,并将其压缩至一个变更集, -使仓库变成一个真正的合并发生的状态,而不会真的生成一个合并提交。 -这意味着你的未来的提交将会只有一个父提交,并允许你引入另一个分支的所有改动, -然后在记录一个新提交前做更多的改动。同样 `--no-commit` 选项在默认合并过程中可以用来延迟生成合并提交。 +`--squash` 选项接受被合并的分支上的所有工作,并将其压缩至一个变更集,使仓库变成一个真正的合并发生的状态,而不会真的生成一个合并提交。 +这意味着你的未来的提交将会只有一个父提交,并允许你引入另一个分支的所有改动,然后在记录一个新提交前做更多的改动。 +同样 `--no-commit` 选项在默认合并过程中可以用来延迟生成合并提交。 现在你可以给维护者发送一条消息,表示你已经做了要求的修改然后他们可以在你的 `featureBv2` 分支上找到那些改动。 @@ -753,12 +738,10 @@ sending 2 messages 100% (2/2) done ---- -此时,你可以到 Drafts 文件夹中,修改收件人字段为想要发送补丁的邮件列表, -可能需要抄送给维护者或负责那个部分的人,然后发送。 +此时,你可以到 Drafts 文件夹中,修改收件人字段为想要发送补丁的邮件列表,可能需要抄送给维护者或负责那个部分的人,然后发送。 你也可以通过一个 SMTP 服务器发送补丁。 -同之前一样,你可以通过一系列的 `git config` 命令来分别设置选项, -或者你可以手动地将它们添加到你的 `~/.gitconfig` 文件的 sendmail 区块: +同之前一样,你可以通过一系列的 `git config` 命令来分别设置选项,或者你可以手动地将它们添加到你的 `~/.gitconfig` 文件的 sendmail 区块: [source,ini] ---- @@ -802,9 +785,18 @@ References: Result: OK ---- +[TIP] +==== +如需获取系统和邮件配置帮助、更多技巧与窍门,以及通过电子邮件发送试用补丁的沙箱环境,请访问 https://git-send-email.io[git-send-email.io^]。 +==== + ==== 总结 -这个部分介绍了处理可能会遇到的几个迥然不同类型的 Git 项目的一些常见的工作流程, -介绍了帮助管理这个过程的一些新工具。 +在本节中,我们介绍了多种工作流程,并探讨了作为小团队成员参与闭源项目与为大型公开项目贡献代码之间的区别。 +你已经知道在提交前检查空格错误,并能够撰写出色的提交信息。 +你还学习了如何格式化补丁文件,并将其发送至开发者邮件列表。 +在不同工作流程的背景下,我们也讨论了如何处理合并操作。 +现在,你已具备充分的能力,可以顺利参与任何项目的协作工作。 + 接下来,你会了解到如何在贡献的另一面工作:维护一个 Git 项目。 你将会学习如何成为一个仁慈的独裁者或整合管理者。 diff --git a/book/05-distributed-git/sections/distributed-workflows.asc b/book/05-distributed-git/sections/distributed-workflows.asc index 4f8600ad..27426ea6 100644 --- a/book/05-distributed-git/sections/distributed-workflows.asc +++ b/book/05-distributed-git/sections/distributed-workflows.asc @@ -3,11 +3,8 @@ (((workflows))) 与传统的集中式版本控制系统(CVCS)相反,Git 的分布式特性使得开发者间的协作变得更加灵活多样。 在集中式系统中,每个开发者就像是连接在集线器上的节点,彼此的工作方式大体相像。 -而在 Git 中,每个开发者同时扮演着节点和集线器的角色——也就是说, -每个开发者既可以将自己的代码贡献到其他的仓库中,同时也能维护自己的公开仓库, -让其他人可以在其基础上工作并贡献代码。 -由此,Git 的分布式协作可以为你的项目和团队衍生出种种不同的工作流程, -接下来的章节会介绍几种利用了 Git 的这种灵活性的常见应用方式。 +而在 Git 中,每个开发者同时扮演着节点和集线器的角色——也就是说,每个开发者既可以将自己的代码贡献到其他的仓库中,同时也能维护自己的公开仓库,让其他人可以在其基础上工作并贡献代码。 +由此,Git 的分布式协作可以为你的项目和团队衍生出种种不同的工作流程,接下来的章节会介绍几种利用了 Git 的这种灵活性的常见应用方式。 我们将讨论每种方式的优点以及可能的缺点;你可以选择使用其中的某一种,或者将它们的特性混合搭配使用。 ==== 集中式工作流 @@ -86,6 +83,18 @@ image::images/benevolent-dictator.png[主管与副主管工作流。] 这种工作流程并不常用,只有当项目极为庞杂,或者需要多级别管理时,才会体现出优势。 利用这种方式,项目总负责人(即主管)可以把大量分散的集成工作委托给不同的小组负责人分别处理,然后在不同时刻将大块的代码子集统筹起来,用于之后的整合。 +[[_patterns_for_managing_source_code_branches]] +==== 管理源代码分支的模式 + +[NOTE] +==== +Martin Fowler 编写了一本名为“Patterns for Managing Source Code Branches”(译:《管理源代码分支的模式》)的指南。 +该指南涵盖了所有常见的 Git 工作流,并说明了如何以及何时使用这些工作流。 +此外,还包含一个比较高频率与低频率整合的章节。 + +https://martinfowler.com/articles/branching-patterns.html[^] +==== + ==== 工作流程总结 上面介绍了在 Git 等分布式系统中经常使用的工作流程,但是在实际的开发中,你会遇到许多可能适合你的特定工作流程的变种。 diff --git a/book/05-distributed-git/sections/maintaining.asc b/book/05-distributed-git/sections/maintaining.asc index 00baf1a2..d405a8cb 100644 --- a/book/05-distributed-git/sections/maintaining.asc +++ b/book/05-distributed-git/sections/maintaining.asc @@ -2,8 +2,7 @@ (((maintaining a project))) 除了如何有效地参与一个项目的贡献之外,你可能也需要了解如何维护项目。 -这包含接受并应用别人使用 `format-patch` 生成并通过电子邮件发送过来的补丁, -或对项目添加的远程版本库分支中的更改进行整合。 +这包含接受并应用别人使用 `format-patch` 生成并通过电子邮件发送过来的补丁,或对项目添加的远程版本库分支中的更改进行整合。 但无论是管理版本库,还是帮忙验证、审核收到的补丁,都需要同其他贡献者约定某种长期可持续的工作方式。 ==== 在主题分支中工作 @@ -39,8 +38,7 @@ $ git checkout -b sc/ruby_client master ===== 使用 `apply` 命令应用补丁 (((git commands, apply))) -如果你收到了一个使用 `git diff` 或 Unix `diff` 命令的变体(不推荐使用这种方式,具体见下一节) -创建的补丁,可以使用 `git apply` 命令来应用。 +如果你收到了一个使用 `git diff` 或 Unix `diff` 命令的变体(不推荐使用这种方式,具体见下一节)创建的补丁,可以使用 `git apply` 命令来应用。 假设你将补丁保存在了 `/tmp/patch-ruby-client.patch` 中,可以这样应用补丁: [source,console] @@ -51,9 +49,7 @@ $ git apply /tmp/patch-ruby-client.patch 这会修改工作目录中的文件。 它与运行 `patch -p1` 命令来应用补丁几乎是等效的,但是这种方式更加严格,相对于 patch 来说,它能够接受的模糊匹配更少。 它也能够处理 `git diff` 格式文件所描述的文件添加、删除和重命名操作,而 `patch` 则不会。 -最后,`git apply` 命令采用了一种“全部应用,否则就全部撤销(apply all or abort all)”的模型, -即补丁只有全部内容都被应用和完全不被应用两个状态,而 `patch` 可能会导致补丁文件被部分应用, -最后使你的工作目录保持在一个比较奇怪的状态。 +最后,`git apply` 命令采用了一种“全部应用,否则就全部撤销(apply all or abort all)”的模型,即补丁只有全部内容都被应用和完全不被应用两个状态,而 `patch` 可能会导致补丁文件被部分应用,最后使你的工作目录保持在一个比较奇怪的状态。 总体来看,`git apply` 命令要比 `patch` 谨慎得多。 并且,它不会为你创建提交——在运行之后,你需要手动暂存并提交补丁所引入的更改。 @@ -77,10 +73,8 @@ error: ticgit.gemspec: patch does not apply 如果可能的话,请鼓励贡献者使用 `format-patch` 而不是 `diff` 来为你生成补丁。 而只有对老式的补丁,你才必须使用 `git apply` 命令。 -要应用一个由 `format-patch` 命令生成的补丁,你应该使用 `git am` 命令 -(该命令的名字 `am` 表示它“应用(Apply)一系列来自邮箱(Mailbox)的补丁”)。 -从技术的角度看,`git am` 是为了读取 mbox 文件而构建的, -mbox 是一种用来在单个文本文件中存储一个或多个电子邮件消息的简单纯文本格式。 +要应用一个由 `format-patch` 命令生成的补丁,你应该使用 `git am` 命令(该命令的名字 `am` 表示它“应用(Apply)一系列来自邮箱(Mailbox)的补丁”)。 +从技术的角度看,`git am` 是为了读取 mbox 文件而构建的,mbox 是一种用来在单个文本文件中存储一个或多个电子邮件消息的简单纯文本格式。 其大致格式如下所示: [source,console] @@ -93,15 +87,11 @@ Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20 ---- -这其实就是你前面看到的 `git format-patch` 命令输出的开始几行, -而同时它也是有效的 mbox 电子邮件格式。 -如果有人使用 `git send-email` 命令将补丁以电子邮件的形式发送给你, -你便可以将它下载为 mbox 格式的文件,之后将 git am 命令指向该文件,它会应用其中包含的所有补丁。 -如果你所使用的邮件客户端能够同时将多封邮件保存为 mbox 格式的文件, -你甚至能够将一系列补丁打包为单个 mbox 文件,并利用 `git am` 命令将它们一次性全部应用。 +这其实就是你前面看到的 `git format-patch` 命令输出的开始几行,而同时它也是有效的 mbox 电子邮件格式。 +如果有人使用 `git send-email` 命令将补丁以电子邮件的形式发送给你,你便可以将它下载为 mbox 格式的文件,之后将 git am 命令指向该文件,它会应用其中包含的所有补丁。 +如果你所使用的邮件客户端能够同时将多封邮件保存为 mbox 格式的文件,你甚至能够将一系列补丁打包为单个 mbox 文件,并利用 `git am` 命令将它们一次性全部应用。 -然而,如果贡献者将 `git format-patch` 生成的补丁文件上传到工单系统或类似的任务处理系统, -你可以先将其保存到本地,之后通过 `git am` 来应用补丁: +然而,如果贡献者将 `git format-patch` 生成的补丁文件上传到工单系统或类似的任务处理系统,你可以先将其保存到本地,之后通过 `git am` 来应用补丁: [source,console] ---- @@ -147,8 +137,7 @@ To restore the original branch and stop patching run "git am --abort". ---- 该命令将会在所有出现问题的文件内加入冲突标记,就和发生冲突的合并或变基操作一样。 -而你解决问题的手段很大程度上也是一样的——即手动编辑那些文件来解决冲突,暂存新的文件, -之后运行 `git am --resolved` 继续应用下一个补丁: +而你解决问题的手段很大程度上也是一样的——即手动编辑那些文件来解决冲突,暂存新的文件,之后运行 `git am --resolved` 继续应用下一个补丁: [source,console] ---- @@ -176,8 +165,7 @@ No changes -- Patch already applied. 比如上面这种情况,如果没有 `-3` 选项的话,这看起来就像是存在一个冲突。 由于使用了 `-3` 选项,该补丁就被干净地应用了 -如果你正在利用一个 mbox 文件应用多个补丁,也可以在交互模式下运行 `am` 命令, -这样在每个补丁之前,它会停住询问你是否要应用该补丁: +如果你正在利用一个 mbox 文件应用多个补丁,也可以在交互模式下运行 `am` 命令,这样在每个补丁之前,它会停住询问你是否要应用该补丁: [source,console] ---- @@ -197,21 +185,18 @@ Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all ==== 检出远程分支 (((branches, remote))) -如果你的贡献者建立了自己的版本库,并且向其中推送了若干修改, -之后将版本库的 URL 和包含更改的远程分支发送给你,那么你可以将其添加为一个远程分支,并且在本地进行合并。 +如果你的贡献者建立了自己的版本库,并且向其中推送了若干修改,之后将版本库的 URL 和包含更改的远程分支发送给你,那么你可以将其添加为一个远程分支,并且在本地进行合并。 -比如 Jessica 向你发送了一封电子邮件,内容是在她的版本库中的 `ruby-client` 分支中有一个很不错的新功能, -为了测试该功能,你可以将其添加为一个远程分支,并在本地检出: +比如 Jessica 向你发送了一封电子邮件,内容是在她的版本库中的 `ruby-client` 分支中有一个很不错的新功能,为了测试该功能,你可以将其添加为一个远程分支,并在本地检出: [source,console] ---- -$ git remote add jessica git://github.com/jessica/myproject.git +$ git remote add jessica https://github.com/jessica/myproject.git $ git fetch jessica $ git checkout -b rubyclient jessica/ruby-client ---- -如果她再次发邮件说另一个分支中包含另一个优秀功能,因为之前已经设置好远程分支了, -你就可以直接进行 `fetch` 和 `checkout` 操作。 +如果她再次发邮件说另一个分支中包含另一个优秀功能,因为之前已经设置好远程分支了,你就可以直接进行 `fetch` 和 `checkout` 操作。 这对于与他人长期合作工作来说很有用。 而对于提交补丁频率较小的贡献者,相对于每个人维护自己的服务器,不断增删远程分支的做法,使用电子邮件来接收可能会比较省时。 @@ -276,9 +261,7 @@ $ git diff master 这是因为 Git 会直接将该主题分支与 `master` 分支的最新提交快照进行比较。 比如说你在 `master` 分支中向某个文件添加了一行内容,那么直接比对最新快照的结果看上去就像是你在主题分支中将这一行删除了。 -如果 `master` 分支是你的主题分支的直接祖先,其实是没有任何问题的; -但是一旦两个分支的历史产生了分叉,上述比对产生的 diff 看上去就像是将主题分支中所有的新东西加入, -并且将 `master` 分支所独有的东西删除。 +如果 `master` 分支是你的主题分支的直接祖先,其实是没有任何问题的;但是一旦两个分支的历史产生了分叉,上述比对产生的 diff 看上去就像是将主题分支中所有的新东西加入,并且将 `master` 分支所独有的东西删除。 而你真正想要检查的东西,实际上仅仅是主题分支所添加的更改——也就是该分支与 `master` 分支合并所要引入的工作。 要达到此目的,你需要让 Git 对主题分支上最新的提交与该分支与 `master` 分支的首个公共祖先进行比较。 @@ -324,8 +307,7 @@ $ git diff master...contrib 在这种情况下,`master` 分支包含的代码是基本稳定的。 当你完成某个主题分支的工作,或审核通过了其他人所贡献的工作时,你会将其合并进入 `master` 分支,之后将主题分支删除,如此反复。 -举例来说,如果我们的版本库包含类似 <> 的两个名称分别为 `ruby_client` 和 `php_client` 的分支, -并且我们合并完 `ruby_client` 分支后,再合并 `php_client` 分支,那么提交历史最后会变成 <> 的样子。 +举例来说,如果我们的版本库包含类似 <> 的两个名称分别为 `ruby_client` 和 `php_client` 的分支,并且我们合并完 `ruby_client` 分支后,再合并 `php_client` 分支,那么提交历史最后会变成 <> 的样子。 [[merwf_a]] .包含若干主题分支的提交历史。 @@ -354,11 +336,9 @@ image::images/merging-workflows-4.png[合并主题分支后。] .一次发布之后。 image::images/merging-workflows-5.png[一次发布之后。] -这样当人们克隆你项目的版本库后,既可以检出 `master` 分支以构建最新的稳定版本并保持更新, -也可以检出包含更多前沿内容 `develop` 分支。 +这样当人们克隆你项目的版本库后,既可以检出 `master` 分支以构建最新的稳定版本并保持更新,也可以检出包含更多前沿内容 `develop` 分支。 你也可以扩展这个概念,维护一个将所有工作合并到一起的整合分支。 -当该分支的代码稳定并通过测试之后,将其合并进入 `develop` 分支; -经过一段时间,确认其稳定之后,将其以快进的形式并入 `master` 分支。 +当该分支的代码稳定并通过测试之后,将其合并进入 `develop` 分支;经过一段时间,确认其稳定之后,将其以快进的形式并入 `master` 分支。 ===== 大项目合并工作流 @@ -374,6 +354,7 @@ image::images/large-merges-1.png[管理复杂的一系列接收贡献的平行 如果主题分支需要更多工作,它则会被并入 `pu` 分支。 当它们完全稳定之后,会被再次并入 `master` 分支。 +随后,`next` 和 `pu` 分支会从 `master` 分支重新构建。 这意味着 `master` 分支始终在进行快进,`next` 分支偶尔会被变基,而 `pu` 分支的变基比较频繁: .将贡献的主题分支并入长期整合分支。 @@ -381,19 +362,16 @@ image::images/large-merges-2.png[将贡献的主题分支并入长期整合分 当主题分支最终被并入 `master` 分支后,便会被从版本库中删除掉。 Git 项目还有一个从上一次发布中派生出来的 `maint` 分支来提供向后移植过来的补丁以供发布维护更新。 -因此,当你克隆 Git 的版本库之后,就会有四个可分别评估该项目开发的不同阶段的可检出的分支, -检出哪个分支,取决于你需要多新的版本,或者你想要如何进行贡献; -对于维护者来说,这套结构化的工作流能帮助它们审查新的贡献。 -Git 项目的工作流是特别的。要清晰地理解它,请阅读 -https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt[Git 维护者手册]。 +因此,当你克隆 Git 的版本库之后,就会有四个可分别评估该项目开发的不同阶段的可检出的分支,检出哪个分支,取决于你需要多新的版本,或者你想要如何进行贡献;对于维护者来说,这套结构化的工作流能帮助它们审查新的贡献。 +Git 项目的工作流是特别的。 +要清晰地理解它,请阅读 https://github.com/git/git/blob/master/Documentation/howto/maintain-git.adoc[Git 维护者手册^]。 [[_rebase_cherry_pick]] ===== 变基与拣选工作流 (((workflows, rebasing and cherry-picking))) 为了保持线性的提交历史,有些维护者更喜欢在 `master` 分支上对贡献过来的工作进行变基和拣选,而不是直接将其合并。 -当你完成了某个主题分支中的工作,并且决定要将其整合的时候,你可以在该分支中运行变基命令, -在当前 `master` 分支(或者是 `develop` 等分支)的基础上重新构造修改。 +当你完成了某个主题分支中的工作,并且决定要将其整合的时候,你可以在该分支中运行变基命令,在当前 `master` 分支(或者是 `develop` 等分支)的基础上重新构造修改。 如果结果理想的话,你可以快进 `master` 分支,最后得到一个线性的项目提交历史。 (((git commands, cherry-pick))) @@ -430,8 +408,7 @@ image::images/rebasing-2.png[拣选主题分支中的一个提交后的历史。 如果你在进行大量的合并或变基,或维护一个长期的主题分支,Git 提供的一个叫做“rerere”的功能会有一些帮助。 Rerere 是“重用已记录的冲突解决方案(reuse recorded resolution)”的意思——它是一种简化冲突解决的方法。 -当启用 rerere 时,Git 将会维护一些成功合并之前和之后的镜像,当 Git 发现之前已经修复过类似的冲突时, -便会使用之前的修复方案,而不需要你的干预。 +当启用 rerere 时,Git 将会维护一些成功合并之前和之后的镜像,当 Git 发现之前已经修复过类似的冲突时,便会使用之前的修复方案,而不需要你的干预。 这个功能包含两个部分:一个配置选项和一个命令。 其中的配置选项是 `rerere.enabled`,把它放在全局配置中就可以了: @@ -444,8 +421,7 @@ $ git config --global rerere.enabled true 现在每当你进行一次需要解决冲突的合并时,解决方案都会被记录在缓存中,以备之后使用。 如果你需要和 rerere 的缓存交互,你可以使用 `git rerere` 命令。 -当单独调用它时,Git 会检查解决方案数据库,尝试寻找一个和当前任一冲突相关的匹配项并解决冲突 -(尽管当 `rerere.enabled` 被设置为 `true` 时会自动进行)。 +当单独调用它时,Git 会检查解决方案数据库,尝试寻找一个和当前任一冲突相关的匹配项并解决冲突(尽管当 `rerere.enabled` 被设置为 `true` 时会自动进行)。 它也有若干子命令,可用来查看记录项,删除特定解决方案和清除缓存全部内容等。 我们将在 <> 中详细探讨。 @@ -509,9 +485,8 @@ $ git show maintainer-pgp-pub | gpg --import ==== 生成一个构建号 (((build numbers)))(((git commands, describe))) -Git 中不存在随每次提交递增的“v123”之类的数字序列,如果你想要为提交附上一个可读的名称, -可以对其运行 `git describe` 命令。作为回应,Git 将会生成一个字符串, -它由最近的标签名、自该标签之后的提交数目和你所描述的提交的部分 SHA-1 值(前缀的 `g` 表示 Git)构成: +Git 中不存在随每次提交递增的“v123”之类的数字序列,如果你想要为提交附上一个可读的名称,可以对其运行 `git describe` 命令。 +作为回应,Git 将会生成一个字符串,它由最近的标签名、自该标签之后的提交数目和你所描述的提交的部分 SHA-1 值(前缀的 `g` 表示 Git)构成: [source,console] ---- @@ -523,12 +498,9 @@ v1.6.2-rc1-20-g8c5b85c 实际上,如果你的 Git 是从 Git 自己的版本库克隆下来并构建的,那么 `git --version` 命令给出的结果是与此类似的。 如果你所描述的提交自身就有一个标签,那么它将只会输出标签名,没有后面两项信息。 -默认情况下, `git describe` 命令需要有注解的标签(即使用 `-a` 或 `-s` 选项创建的标签); -如果你想使用轻量标签(无注解的标签),请在命令后添加 `--tags` 选项。 -你也可以使用这个字符串来调用 `git checkout` 或 `git show` 命令, -但是这依赖于其末尾的简短 SHA-1 值,因此不一定一直有效。 -比如,最近 Linux 内核为了保证 SHA-1 值对象的唯一性,将其位数由 8 位扩展到了 10 位, -导致以前的 `git describe` 输出全部失效。 +默认情况下, `git describe` 命令需要有注解的标签(即使用 `-a` 或 `-s` 选项创建的标签);如果你想使用轻量标签(无注解的标签),请在命令后添加 `--tags` 选项。 +你也可以使用这个字符串来调用 `git checkout` 或 `git show` 命令,但是这依赖于其末尾的简短 SHA-1 值,因此不一定一直有效。 +比如,最近 Linux 内核为了保证 SHA-1 值对象的唯一性,将其位数由 8 位扩展到了 10 位,导致以前的 `git describe` 输出全部失效。 [[_preparing_release]] ==== 准备一次发布 diff --git a/book/06-github/sections/1-setting-up-account.asc b/book/06-github/sections/1-setting-up-account.asc index a11a0d48..db79a4ab 100644 --- a/book/06-github/sections/1-setting-up-account.asc +++ b/book/06-github/sections/1-setting-up-account.asc @@ -2,7 +2,7 @@ (((GitHub, user accounts))) 你所需要做的第一件事是创建一个免费账户。 -直接访问 https://github.com[],选择一个未被占用的用户名,提供一个电子邮件地址和密码,点击写着“Sign up for GitHub”的绿色大按钮即可。 +直接访问 https://github.com[^],选择一个未被占用的用户名,提供一个电子邮件地址和密码,点击写着“Sign up for GitHub”的绿色大按钮即可。 .GitHub 注册表单。 image::images/signup.png[GitHub 注册表单。] @@ -16,7 +16,7 @@ GitHub 会给你提供的邮件地址发送一封验证邮件。 GitHub 为免费账户提供了几乎所有的功能,除了一些高级的特性。 GitHub 的付费计划包含一些高级工具和功能,不过本书将不涉及这部分内容。 -关于可选方案及其对比的更多信息见 https://github.com/pricing[]。 +关于可选方案及其对比的更多信息见 https://github.com/pricing[^]。 ==== 点击屏幕左上角的章鱼猫(Octocat)图标,你将来到控制面板页面。 @@ -40,8 +40,7 @@ image::images/account-settings.png[“Account settings”链接。] .“SSH keys”链接。 image::images/ssh-keys.png[“SSH keys”链接。] -在这个页面点击“`Add an SSH key`”按钮,给你的公钥起一个名字,将你的 `~/.ssh/id_rsa.pub` -(或者自定义的其它名字)公钥文件的内容粘贴到文本区,然后点击“Add key”。 +在这个页面点击“`Add an SSH key`”按钮,给你的公钥起一个名字,将你的 `~/.ssh/id_rsa.pub` (或者自定义的其它名字)公钥文件的内容粘贴到文本区,然后点击“Add key”。 [NOTE] ==== @@ -70,8 +69,7 @@ image::images/avatar-crop.png[裁剪已上传的头像。] ==== 邮件地址 GitHub 使用用户邮件地址区分 Git 提交。 -如果你在自己的提交中使用了多个邮件地址,希望 GitHub 可以正确地将它们连接起来, -你需要在管理页面的 Emails 部分添加你拥有的所有邮箱地址。 +如果你在自己的提交中使用了多个邮件地址,希望 GitHub 可以正确地将它们连接起来,你需要在管理页面的 Emails 部分添加你拥有的所有邮箱地址。 [[_add_email_addresses]] .添加邮件地址 diff --git a/book/06-github/sections/2-contributing.asc b/book/06-github/sections/2-contributing.asc index 7ac90d24..22f1f544 100644 --- a/book/06-github/sections/2-contributing.asc +++ b/book/06-github/sections/2-contributing.asc @@ -25,7 +25,6 @@ image::images/forkbutton.png[“Fork”按钮.] 稍等片刻,你将被转到新项目页面,该项目包含可写的代码副本。 - [[_github_flow]] ==== GitHub 流程 @@ -49,14 +48,22 @@ GitHub 设计了一个以拉取请求为中心的特殊合作流程。 现在我们来看一个使用这个流程的例子。 +[TIP] +==== +大多数情况下,可以使用官方的 *GitHub CLI* 工具,而不是通过 GitHub 网页界面进行操作。 +该工具支持 Windows、macOS 和 Linux 系统。 +请访问 https://cli.github.com/[GitHub CLI 首页^] 获取安装说明和使用手册。 +==== + ===== 创建拉取请求 -Tony 在找一些能在他的 Arduino 微控制器上运行的代码,他觉得 https://github.com/schacon/blink[] 中的代码不错。 +Tony 在找一些能在他的 Arduino 微控制器上运行的代码,他觉得 https://github.com/schacon/blink[^] 中的代码不错。 .他想要做出贡献的项目 image::images/blink-01-start.png[他想要做出贡献的项目] -但是有个问题,这个代码中的闪烁频率太高,我们觉得 3 秒一次比 1 秒一次更好一些。 +但是有个问题,这个代码中的闪烁频率太高, +我们觉得 3 秒一次比 1 秒一次更好一些。 所以让我们来改进这个程序,并将修改后的代码提交给这个项目。 首先,单击“Fork”按钮来获得这个项目的副本。 @@ -113,8 +120,7 @@ To https://github.com/tonychacon/blink <5> 将改动提交到分支中 <6> 将新分支推送到 GitHub 的副本中 -现在到 GitHub 上查看之前的项目副本,可以看到 GitHub 提示我们有新的分支, -并且显示了一个大大的绿色按钮让我们可以检查我们的改动,并给源项目创建拉取请求。 +现在到 GitHub 上查看之前的项目副本,可以看到 GitHub 提示我们有新的分支,并且显示了一个大大的绿色按钮让我们可以检查我们的改动,并给源项目创建拉取请求。 你也可以到“Branches”(分支)页面查看分支并创建拉取请求: `\https://github.com/<用户名>/<项目名>/branches` @@ -123,8 +129,7 @@ image::images/blink-02-pr.png[拉取请求按钮] (((GitHub, pull requests))) 如果我们点击那个绿色按钮,就会跳到一个新页面,在这里我们可以为拉取请求填写标题和描述。 -花点时间编写一个清晰有用的描述是非常值得的,这能让原项目拥有者明白你做了什么, -为什么这个改动是正确的,以及接受此更改是否能够改进他的项目。 +花点时间编写一个清晰有用的描述是非常值得的,这能让原项目拥有者明白你做了什么,为什么这个改动是正确的,以及接受此更改是否能够改进他的项目。 同时我们也能看到比主分支中所“领先”(ahead)的提交(在这个例子中只有一个)以及所有将会被合并的改动与之前代码的对比。 @@ -144,8 +149,7 @@ image::images/blink-03-pull-request-open.png[拉取请求创建页面] 现在,项目的拥有者可以看到你的改动并合并它,拒绝它或是发表评论。 在这里我们就当作他喜欢这个点子,但是他想要让灯熄灭的时间比点亮的时间稍长一些。 -接下来可能会通过电子邮件进行互动,就像我们在 <> -中提到的工作流程那样,但是在 GitHub,这些都在线上完成。 +接下来可能会通过电子邮件进行互动,就像我们在 <> 中提到的工作流程那样,但是在 GitHub,这些都在线上完成。 项目的拥有者可以审查修改,只需要单击某一行,就可以对其发表评论。 .对拉取请求内的特定一行发表评论 @@ -158,47 +162,45 @@ image::images/blink-04-pr-comment.png[拉取请求中对某一行的评论] .通过电子邮件发送的评论提醒 image::images/blink-04-email.png[电子邮件提醒] -每个人都能在拉取请求中发表评论。在 <<_pr_discussion>> 里我们可以看到项目拥有者对某行代码发表评论, -并在讨论区留下了一个普通评论。你可以看到被评论的代码也会在互动中显示出来。 +每个人都能在拉取请求中发表评论。 +在 <<_pr_discussion>> 里我们可以看到项目拥有者对某行代码发表评论,并在讨论区留下了一个普通评论。 +你可以看到被评论的代码也会在互动中显示出来。 [[_pr_discussion]] .拉取请求讨论页面 image::images/blink-05-general-comment.png[拉取请求讨论页面] -现在贡献者可以看到如何做才能让他们的改动被接受。幸运的是,这也是一件轻松的事情。 -如果你使用的是电子邮件进行交流,你需要再次对代码进行修改并重新提交至邮件列表, -这些修改会自动更新到拉取请求上。在 <<_pr_final>> 中,你也可以在更新后的拉取请求中看到已折叠的旧代码评论, -因为它是在修改后的行上添加的评论。 +现在贡献者可以看到如何做才能让他们的改动被接受。 +幸运的是,这也是一件轻松的事情。 +如果你使用的是电子邮件进行交流,你需要再次对代码进行修改并重新提交至邮件列表,这些修改会自动更新到拉取请求上。 +在 <<_pr_final>> 中,你也可以在更新后的拉取请求中看到已折叠的旧代码评论,因为它是在修改后的行上添加的评论。 -对现有的拉取请求添加提交并不会触发提醒,因此 Tony 在推送了他的修正后, -还需要通过评论告知项目拥有者他完成了修改请求。 +对现有的拉取请求添加提交并不会触发提醒,因此 Tony 在推送了他的修正后,还需要通过评论告知项目拥有者他完成了修改请求。 [[_pr_final]] .最终的拉取请求 image::images/blink-06-final.png[最终的拉取请求] -如果你点开拉取请求的“Files Changed”(更改的文件)选项卡,你将会看到“整理过的”差异表 -—— 也就是这个分支被合并到主分支之后将会产生的所有改动, +如果你点开拉取请求的“Files Changed”(更改的文件)选项卡,你将会看到“整理过的”差异表 —— 也就是这个分支被合并到主分支之后将会产生的所有改动, 其实就是 `git diff master...<分支名>` 命令的执行结果。 你可以浏览 <> 来了解更多关于差异表的知识。 你还会注意到,GitHub 会检查你的拉取请求是否能直接合并,如果可以,将会提供一个按钮来进行合并操作。 这个按钮只在你对版本库有写入权限并且可以进行简洁合并时才会显示。 -你点击后 GitHub 将做出一个“非快进式”(non-fast-forward)合并, -即使这个合并 *能够* 快进式(fast-forward)合并,GitHub 依然会创建一个合并提交。 +你点击后 GitHub 将做出一个“非快进式”(non-fast-forward)合并,即使这个合并 *能够* 快进式(fast-forward)合并,GitHub 依然会创建一个合并提交。 如果你需要,你还可以将分支拉取并在本地合并。 如果你将这个分支合并到 `master` 分支中并推送到 GitHub,这个拉取请求会被自动关闭。 -这就是大部分 GitHub 项目使用的工作流程。创建分支,基于分支创建拉取请求,进行讨论, -根据需要继续在分支上进行修改,最终关闭或合并拉取请求。 +这就是大部分 GitHub 项目使用的工作流程。 +创建分支,基于分支创建拉取请求,进行讨论,根据需要继续在分支上进行修改,最终关闭或合并拉取请求。 [NOTE] .不必总是 Fork ==== 有件很重要的事情:你可以在同一个版本库中不同的分支提交拉取请求。 -如果你正在和某人实现某个功能,而且你对项目有写权限,你可以推送分支到版本库, -并在 `master` 分支提交一个拉取请求并在此进行代码审查和讨论的操作。不需要进行“Fork”。 +如果你正在和某人实现某个功能,而且你对项目有写权限,你可以推送分支到版本库,并在 `master` 分支提交一个拉取请求并在此进行代码审查和讨论的操作。 +不需要进行“Fork”。 ==== ==== 拉取请求的进阶用法 @@ -207,14 +209,12 @@ image::images/blink-06-final.png[最终的拉取请求] ===== 将拉取请求制作成补丁 -有一件重要的事情:和大部分通过邮件列表工作的项目对补丁贡献的看法一样, -许多项目并不认为拉取请求可以作为补丁。 +有一件重要的事情:和大部分通过邮件列表工作的项目对补丁贡献的看法一样,许多项目并不认为拉取请求可以作为补丁。 大多数的 GitHub 项目将拉取请求的分支当作对改动的交流方式,并将变更集合起来统一进行合并。 这是个重要的差异,因为一般来说改动会在代码完成前提出,这和基于邮件列表的补丁贡献有着天差地别。 这使得维护者们可以更早的沟通,由社区中的力量能提出更好的方案。 -当有人从拉取请求提交了一些代码,并且维护者和社区提出了一些意见,这个补丁系列并不需要从头来过, -只需要将改动重新提交并推送到分支中,这使得讨论的背景和过程可以齐头并进。 +当有人从拉取请求提交了一些代码,并且维护者和社区提出了一些意见,这个补丁系列并不需要从头来过,只需要将改动重新提交并推送到分支中,这使得讨论的背景和过程可以齐头并进。 举个例子,你可以回去看看 <<_pr_final>>,你会注意到贡献者没有变基他的提交再提交一个新的拉取请求, 而是直接增加了新的提交并推送到已有的分支中。 @@ -232,17 +232,16 @@ image::images/pr-01-fail.png[拉取请求合并失败] 如果你看到了像 <<_pr_fail>> 中的画面,你就需要修复你的分支让这个提示变成绿色,这样维护者就不需要再做额外的工作。 -你有两种方法来解决这个问题。你可以把你的分支变基到目标分支中去 -(通常是你派生出的版本库中的 `master` 分支),或者你可以合并目标分支到你的分支中去。 +你有两种方法来解决这个问题。 +你可以把你的分支变基到目标分支中去(通常是你派生出的版本库中的 `master` 分支),或者你可以合并目标分支到你的分支中去。 GitHub 上的大多数的开发者会使用后一种方法,基于我们在上一节提到的理由: -我们最看重的是历史记录和最后的合并,变基除了给你带来看上去简洁的历史记录, -只会让你的工作变得更加困难且更容易犯错。 +我们最看重的是历史记录和最后的合并,变基除了给你带来看上去简洁的历史记录,只会让你的工作变得更加困难且更容易犯错。 如果你想要合并目标分支来让你的拉取请求变得可合并,你需要将源版本库添加为一个新的远端,并从远端抓取内容,合并主分支的内容到你的分支中去,修复所有的问题并最终重新推送回你提交拉取请求使用的分支。 -在这个例子中,我们再次使用之前的“tonychacon”用户来进行示范,源作者提交了一个改动, -使得拉取请求和它产生了冲突。现在来看我们解决这个问题的步骤。 +在这个例子中,我们再次使用之前的“tonychacon”用户来进行示范,源作者提交了一个改动,使得拉取请求和它产生了冲突。 +现在来看我们解决这个问题的步骤。 [source,console] ---- @@ -289,8 +288,8 @@ To https://github.com/tonychacon/blink .拉取请求现在可以干净地合并了 image::images/pr-02-merge-fix.png[修复了的拉取请求] -Git 的伟大之处就是你可以一直重复以上操作。如果你有一个运行了十分久的项目, -你可以轻松地合并目标分支且只需要处理最近的一次冲突,这使得管理流程更加容易。 +Git 的伟大之处就是你可以一直重复以上操作。 +如果你有一个运行了十分久的项目,你可以轻松地合并目标分支且只需要处理最近的一次冲突,这使得管理流程更加容易。 如果你一定想对分支做变基并进行清理,你可以这么做,但是强烈建议你不要强行地提交到已经提交了拉取请求的分支。 如果其他人拉取了这个分支并进行一些修改,你将会遇到 <> 中提到的问题。 @@ -301,14 +300,14 @@ Git 的伟大之处就是你可以一直重复以上操作。如果你有一个 你的下个问题可能是“我该如何引用旧的拉取请求?”。 有许多方法可以让你在 GitHub 上的几乎任何地方引用其他东西。 -先从如何对拉取请求或议题(Issue)进行相互引用开始。所有的拉取请求和议题在项目中都会有一个独一无二的编号。 -举个例子,你无法同时拥有 3 号拉取请求和 3 号议题。如果你想要引用任何一个拉取请求或议题, -你只需要在提交或描述中输入 `+#<编号>+` 即可。 -你也可以指定引用其他版本库的议题或拉取请求,如果你想要引用其他人对该版本库的“Fork”中的议题或拉取请求, -输入 `用户名#<编号>` ,如果在不同的版本库中,输入 `用户名/版本库名#<编号>` 。 +先从如何对拉取请求或议题(Issue)进行相互引用开始。 +所有的拉取请求和议题在项目中都会有一个独一无二的编号。 +举个例子,你无法同时拥有 3 号拉取请求和 3 号议题。 +如果你想要引用任何一个拉取请求或议题,你只需要在提交或描述中输入 `+#<编号>+` 即可。 +你也可以指定引用其他版本库的议题或拉取请求,如果你想要引用其他人对该版本库的“Fork”中的议题或拉取请求,输入 `用户名#<编号>` ,如果在不同的版本库中,输入 `用户名/版本库名#<编号>` 。 -我们来看一个例子。假设我们对上个例子中的分支进行了变基,并为此创建一个新的拉取请求, -现在我们希望能在新的拉取请求中引用旧的拉取请求。 +我们来看一个例子。 +假设我们对上个例子中的分支进行了变基,并为此创建一个新的拉取请求,现在我们希望能在新的拉取请求中引用旧的拉取请求。 我们同时希望引用一个派生出的项目中的议题和一个完全不同的项目中的议题, 就可以像 <<_pr_references>> 这样填写描述。 @@ -324,8 +323,7 @@ image::images/mentions-02-render.png[渲染后的拉取请求中的引用] 你会注意到完整的 GitHub 地址被简化了,只留下了必要的信息。 -如果 Tony 回去关闭了源拉取请求,我们可以看到一个被引用的提示, -GitHub 会自动的反向追踪事件并显示在拉取请求的时间轴上。 +如果 Tony 回去关闭了源拉取请求,我们可以看到一个被引用的提示,GitHub 会自动的反向追踪事件并显示在拉取请求的时间轴上。 这意味着任何查看这个拉取请求的人可以轻松地访问新的拉取请求。 这个链接就像 <<_pr_closed>> 中展示的那样。 @@ -349,12 +347,10 @@ Markdown 可以让你输入纯文本,但是渲染出丰富的内容。 .一个 Markdown 的示例和渲染效果 image::images/markdown-01-example.png[Markdown 示例] -===== GitHub 风格的 Markdown - GitHub 风格的 Markdown 增加了一些基础的 Markdown 中做不到的东西。 它在创建拉取请求和议题中的评论和描述时十分有用。 -====== 任务列表 +===== 任务列表 第一个 GitHub 专属的 Markdown 功能,特别是用在拉取请求中,就是任务列表。 一个任务列表可以展示出一系列你想要完成的事情,并带有复选框。 @@ -391,7 +387,8 @@ image::images/markdown-03-task-summary.png[任务列表示例] ===== 代码片段 -你也可以在评论中添加代码片段。这在你想要展示尚未提交到分支中的代码时会十分有用。 +你也可以在评论中添加代码片段。 +这在你想要展示尚未提交到分支中的代码时会十分有用。 它也经常被用在展示无法正常工作的代码或这个拉取请求需要的代码。 你需要用“反引号”将需要添加的代码片段包起来。 @@ -437,8 +434,10 @@ image::images/markdown-05-quote.png[渲染后的引用] ===== 表情符号 -最后,我们可以在评论中使用表情符号。这经常出现在 GitHub 的议题和拉取请求的评论中。 -GitHub 上甚至有表情助手。如果你在输入评论时以 `:` 开头,自动完成器会帮助你找到你需要的表情。 +最后,我们可以在评论中使用表情符号。 +这经常出现在 GitHub 的议题和拉取请求的评论中。 +GitHub 上甚至有表情助手。 +如果你在输入评论时以 `:` 开头,自动完成器会帮助你找到你需要的表情。 [[_md_emoji_auto]] .表情符号自动完成器 @@ -466,12 +465,12 @@ image::images/markdown-07-emoji.png[Emoji] 虽然这个功能并不是非常实用,但是它在这种不方便表达感情的媒体里,加入了趣味的元素。 - [NOTE] ==== -事实上现在已经有大量的在线服务可以使用表情符号,这里有个列表可以让你快速的找到能表达你的情绪的表情符号: +事实上现在已经有大量的在线服务可以使用表情符号, +这里有个列表可以让你快速的找到能表达你的情绪的表情符号: -https://www.webfx.com/tools/emoji-cheat-sheet/ +https://www.webfx.com/tools/emoji-cheat-sheet/[^] ==== ===== 图片 @@ -500,8 +499,8 @@ This branch is 5 commits behind progit:master. 但你的 GitHub 仓库不会被 GitHub 自动更新,这件事必须由你自己来做。还好,这事儿很简单。 -第一种方法无需配置。例如,若你从 `https://github.com/progit/progit2.git` 派生了项目, -你可以像这样更新你的 `master` 分支: +第一种方法无需配置。 +例如,若你从 `https://github.com/progit/progit2.git` 派生了项目,你可以像这样更新你的 `master` 分支: [source,console] ---- @@ -514,18 +513,22 @@ $ git push origin master <3> <2> 从 `https://github.com/progit/progit2.git` 抓取更改后合并到 `master` <3> 将 `master` 分支推送到 `origin` -这虽然可行,但每次都要输入从哪个 URL 抓取有点麻烦。你可以稍微设置一下来自动完成它: +这虽然可行,但每次都要输入从哪个 URL 抓取有点麻烦。 +你可以稍微设置一下来自动完成它: [source,console] ---- $ git remote add progit https://github.com/progit/progit2.git <1> -$ git branch --set-upstream-to=progit/master master <2> -$ git config --local remote.pushDefault origin <3> +$ git fetch progit <2> +$ git branch --set-upstream-to=progit/master master <3> +$ git config --local remote.pushDefault origin <4> ---- -<1> 添加源仓库并取一个名字,这里叫它 `progit` -<2> 将 `master` 分支设置为从 `progit` 远端抓取 -<3> 将默认推送仓库设置为 `origin` +<1> 添加源仓库并取一个名字, + 这里叫它 `progit` +<2> 获取 progit 仓库的分支信息,特别是 `master` 分支 +<3> 将 `master` 分支设置为从 `progit` 远端抓取 +<4> 将默认推送仓库设置为 `origin` 搞定之后,工作流程为更加简单: @@ -540,6 +543,6 @@ $ git push <3> <2> 从 `progit` 抓取更改后合并到 `master` <3> 将 `master` 分支推送到 `origin` -这种方法可能很有用,但也不是没有缺点。如果你向 `master` 提交,再从 `progit` 中拉取,然后推送到 -`origin`,Git 会很乐意安静地为您完成这项工作,但不会警告你——所有这些操作在以上设置下都是有效的。 +这种方法可能很有用,但也不是没有缺点。 +如果你向 `master` 提交,再从 `progit` 中拉取,然后推送到`origin`,Git 会很乐意安静地为您完成这项工作,但不会警告你——所有这些操作在以上设置下都是有效的。 所以你必须注意永远不要直接提交到 `master`,因为该分支实际上属于上游仓库。 diff --git a/book/06-github/sections/3-maintaining.asc b/book/06-github/sections/3-maintaining.asc index b1bb24c5..f472bad7 100644 --- a/book/06-github/sections/3-maintaining.asc +++ b/book/06-github/sections/3-maintaining.asc @@ -27,8 +27,7 @@ image::images/newrepoform.png[“new repository” 表单。] 我们不会在这里详细说明此项,如果你需要复习,去看 <>。 现在你的项目就托管在 GitHub 上了,你可以把 URL 给任何你想分享的人。 -GitHub 上的项目可通过 HTTP 或 SSH 访问,HTTPS 为 `\https://github.com//` , - SSH 为 `git@github.com:/` 。 +GitHub 上的项目可通过 HTTP 或 SSH 访问,HTTPS 为 `\https://github.com//` , SSH 为 `\git@github.com:/` 。 Git 可以通过以上两种 URL 进行抓取和推送,但是用户的访问权限又因连接时使用的证书不同而异。 [NOTE] @@ -52,7 +51,7 @@ image::images/reposettingslink.png[版本库设置链接.] 然后从左侧菜单中选择 “Collaborators” 。 然后,在输入框中填写用户名,点击 “Add collaborator.” 如果你想授权给多个人,你可以多次重复这个步骤。 -如果你想收回权限,点击他们同一行右侧的 “X” +如果你想收回权限,点击他们同一行右侧的 “X”。 .版本库合作者. image::images/collaborators.png[版本库合作者.] @@ -96,10 +95,10 @@ $ curl https://github.com/tonychacon/fade/pull/1.patch | git am ===== 在合并请求上进行合作 就像我们在 <> 中说过的,现在你可以跟开启合并请求的人进行会话。 -你既可以对某些代码发表评论,也可以对整个提交或整个合并请求发表评论, -在任何地方都可以用 GitHub 风格的 Markdown。 +你既可以对某些代码发表评论,也可以对整个提交或整个合并请求发表评论,在任何地方都可以用 GitHub 风格的 Markdown。 -每次有人在合并请求上发表了评论,你都会收到邮件,通知你哪里发生了改变。邮件里面包含一个链接,指向改变的位置,你可以直接在邮件中回复,相当于在合并请求上发表评论。 +每次有人在合并请求上发表了评论,你都会收到邮件,通知你哪里发生了改变。 +邮件里面包含一个链接,指向改变的位置,你可以直接在邮件中回复,相当于在合并请求上发表评论。 .回复邮件会包含在帖子(thread)中。 image::images/maint-03-email-resp.png[邮件回复] @@ -126,11 +125,10 @@ image::images/maint-02-merge.png[合并按钮] 实际上 GitHub 在服务器上把合并请求分支视为一种 “假分支”。 默认情况下你克隆时不会得到它们,但它们还是隐式地存在,你可以很容易地访问到它们。 -为了展示这个,我们要用到一个叫做 `ls-remote` 的低级命令(通常被叫做“plumbing”, -我们会在 <> 读到更多相关内容)。 +为了展示这个,我们要用到一个叫做 `ls-remote` 的低级命令(通常被叫做“plumbing”,我们会在 <> 读到更多相关内容)。 这个命令在日常 Git 操作中基本不会用到,但在显示服务器上有哪些引用(reference)时很管用。 -如果在我们之前用过的 “blink” 版本库上使用这个命令,我们会得到一个版本库里所有的分支,标签和其它引用(reference)的列表。 +如果在我们之前用过的 “blink” 版本库上使用这个命令,我们会得到一个版本库里所有的分支、标签和其它引用(reference)的列表。 [source,console] ---- @@ -148,13 +146,10 @@ a5a7751a33b7e86c5e9bb07b26001bb17d775d1a refs/pull/4/head 当然,如果你在你自己的版本库或其它你想检查的远程版本库中使用 `git ls-remote origin` ,它会显示相似的内容。 如果版本库在 GitHub 上并且有打开的合并请求,你会得到一些以 `refs/pull/` 开头的引用。 -它们实际上是分支,但因为它们不在 `refs/heads/` 中,所以正常情况下你克隆时不会从服务器上得到它们 -——抓取过程正常情况下会忽略它们。 +它们实际上是分支,但因为它们不在 `refs/heads/` 中,所以正常情况下你克隆时不会从服务器上得到它们——抓取过程正常情况下会忽略它们。 每个合并请求有两个引用——其中以 `/head` 结尾的引用指向的提交记录与合并请求分支中的最后一个提交记录是同一个。 -所以如果有人在我们的版本库中开启了一个合并请求,他们的分支叫做 `bug-fix`, -指向 `a5a775` 这个提交记录,那么在 *我们的* 版本库中我们没有 `bug-fix` 分支(因为那是在他们的 fork 中), -但我们 *可以* 有一个 `pull//head` 指向 `a5a775`。 +所以如果有人在我们的版本库中开启了一个合并请求,他们的分支叫做 `bug-fix`,指向 `a5a775` 这个提交记录,那么在 *我们的* 版本库中我们没有 `bug-fix` 分支(因为那是在他们的 fork 中),但我们 *可以* 有一个 `pull//head` 指向 `a5a775`。 这意味着我们可以很容易地拉取每一个合并请求分支而不用添加一堆远程仓库。 现在,你可以像直接抓取引用一样抓取那些分支或提交。 @@ -223,7 +218,6 @@ Switched to a new branch 'pr/2' 在 GitHub 那边也有一个 `refs/pull/#/merge` 引用,它代表的是如果你在网站上按了 “merge” 按钮对应的提交记录。 这甚至让你可以在按按钮之前就测试这个合并。 - ===== 合并请求之上的合并请求 你不仅可以在主分支或者说 `master` 分支上开启合并请求,实际上你可以在网络上的任何一个分支上开启合并请求。 @@ -330,7 +324,7 @@ X-GitHub-Recipient-Address: tchacon@example.com 第一个就是 `README` 文件,可以是几乎任何 GitHub 可以识别的格式。 例如,它可以是 `README` ,`README.md` , `README.asciidoc` 。 -如果 GitHub 在你的版本库中找到 README 文件,会把它在项目的首页渲染出来。 +如果 GitHub 在你的版本库中找到 `README` 文件,会把它在项目的首页渲染出来。 很多团队在这个文件里放版本库或项目新人需要了解的所有相关的信息。 它一般包含这些内容: @@ -352,7 +346,7 @@ X-GitHub-Recipient-Address: tchacon@example.com .开启合并请求时有 CONTRIBUTING 文件存在. image::images/maint-09-contrib.png[贡献注意事项] -这个的作用就是你可以在这里指出对于你的项目开启的合并请求你想要的/不想要的各种事情。 +这个的作用就是你可以在这里指出对于你的项目开启的合并请求你想要的/不想要的各种事情。 这样别人在开启合并请求之前可以读到这些指导方针。 ==== 项目管理 diff --git a/book/06-github/sections/5-scripting.asc b/book/06-github/sections/5-scripting.asc index 44f41e88..6566fdcd 100644 --- a/book/06-github/sections/5-scripting.asc +++ b/book/06-github/sections/5-scripting.asc @@ -51,8 +51,7 @@ image::images/scripting-03-webhook.png[Web 钩子配置] Web 钩子的设置非常简单。 大多数情况下只需要输入一个 URL 与一个密钥然后点击 “Add webhook”。 -有几个选项可以指定在哪个事件时想要 GitHub 发送请求—— -默认的行为是只有当某人推送新代码到仓库的任一分支时的 `push` 事件获得一个请求。 +有几个选项可以指定在哪个事件时想要 GitHub 发送请求——默认的行为是只有当某人推送新代码到仓库的任一分支时的 `push` 事件获得一个请求。 让我们看一个设置处理 web 钩子的 web 服务的小例子。 我们将会使用 Ruby web 框架 Sinatra,因为它相当简洁,应该能够轻松地看到我们正在做什么。 @@ -108,8 +107,7 @@ image::images/scripting-04-webhook-debug.png[Web 钩子调试信息] 开发者控制台的另一个很棒的功能是可以轻松地重新发送任何请求来测试你的服务。 -关于如何编写 web 钩子与所有可监听的不同事件类型的更多信息,请访问在 -https://docs.github.com/cn/developers/webhooks-and-events/webhooks/about-webhooks[] 的 GitHub 开发者文档。 +关于如何编写 web 钩子与所有可监听的不同事件类型的更多信息,请访问在 https://docs.github.com/cn/developers/webhooks-and-events/webhooks/about-webhooks[^] 的 GitHub 开发者文档。 ==== GitHub API @@ -167,7 +165,6 @@ hs_err_pid* } ---- - ==== 在一个问题上评论 然而,如果想要在网站上进行一个操作,如在 Issue 或 Pull Request 上评论,或者想要查看私有内容或与其交互,你需要授权。 @@ -279,12 +276,9 @@ end ---- 希望这相当容易做。 -在这个 web 钩子处理器中我们浏览刚刚推送上来的每一个提交,在提交信息中查找字符串 -'Signed-off-by' 并且最终使用 HTTP 向 `/repos///statuses/` -API 接口发送一个带有状态的 POST 请求。 +在这个 web 钩子处理器中我们浏览刚刚推送上来的每一个提交,在提交信息中查找字符串 'Signed-off-by' 并且最终使用 HTTP 向 `/repos///statuses/` API 接口发送一个带有状态的 POST 请求。 -在本例中可以发送一个状态('success', 'failure', 'error')、一个发生了什么的描述信息、 -一个用户可以了解更多信息的目标 URL 与一个 “context” 以防一个单独的提交有多个状态。 +在本例中可以发送一个状态('success', 'failure', 'error')、一个发生了什么的描述信息、一个用户可以了解更多信息的目标 URL 与一个 “context” 以防一个单独的提交有多个状态。 例如,一个测试服务可以提供一个状态与一个类似这样的验证服务也可能提供一个状态 -- “context” 字段是用来区别它们的。 如果某人在 GitHub 中打开了一个新的拉取请求并且这个钩子已经设置,会看到类似 <<_commit_status>> 的信息。 @@ -301,7 +295,7 @@ image::images/scripting-07-status.png[提交状态] 尽管我们在这些例子中都是通过 `curl` 与基本的 HTTP 请求来做几乎所有的事情,还有一些以更自然的方式利用 API 的开源库存在着。 在写这篇文章的时候,被支持的语言包括 Go、Objective-C、Ruby 与 .NET。 -访问 https://github.com/octokit[] 了解更多相关信息,它们帮你处理了更多 HTTP 相关的内容。 +访问 https://github.com/octokit[^] 了解更多相关信息,它们帮你处理了更多 HTTP 相关的内容。 希望这些工具能帮助你自定义与修改 GitHub 来更好地为特定的工作流程工作。 -关于全部 API 的完整文档与常见任务的指南,请查阅 https://docs.github.com/cn[]。 +关于全部 API 的完整文档与常见任务的指南,请查阅 https://docs.github.com/cn[^]。 diff --git a/book/07-git-tools/sections/advanced-merging.asc b/book/07-git-tools/sections/advanced-merging.asc index a0d87b47..ae3f388b 100644 --- a/book/07-git-tools/sections/advanced-merging.asc +++ b/book/07-git-tools/sections/advanced-merging.asc @@ -2,8 +2,7 @@ === 高级合并 在 Git 中合并是相当容易的。 -因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支, -经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。 +因为 Git 使多次合并另一个分支变得很容易,这意味着你可以有一个始终保持最新的长期分支,经常解决小的冲突,比在一系列提交后解决一个巨大的冲突要好。 然而,有时也会有棘手的冲突。 不像其他的版本控制系统,Git 并不会尝试过于聪明的合并冲突解决方案。 @@ -15,8 +14,7 @@ Git 的哲学是聪明地决定无歧义的合并方案,但是如果有冲突 ==== 合并冲突 -我们在 <> 介绍了解决合并冲突的一些基础知识, -对于更复杂的冲突,Git 提供了几个工具来帮助你指出将会发生什么以及如何更好地处理冲突。 +我们在 <> 介绍了解决合并冲突的一些基础知识,对于更复杂的冲突,Git 提供了几个工具来帮助你指出将会发生什么以及如何更好地处理冲突。 首先,在做一次可能有冲突的合并前尽可能保证工作目录是干净的。 如果你有正在做的工作,要么提交到一个临时分支要么储藏它。 @@ -37,8 +35,7 @@ end hello() ---- -在我们的仓库中,创建一个名为 `whitespace` 的新分支并将所有 Unix 换行符修改为 DOS 换行符, -实质上虽然改变了文件的每一行,但改变的都只是空白字符。 +在我们的仓库中,创建一个名为 `whitespace` 的新分支并将所有 Unix 换行符修改为 DOS 换行符,实质上虽然改变了文件的每一行,但改变的都只是空白字符。 然后我们修改行 “hello world” 为 “hello mundo”。 [source,console] @@ -137,13 +134,11 @@ $ git status -sb ===== 忽略空白 在这个特定的例子中,冲突与空白有关。 -我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易, -因为每一行都被移除而在另一边每一行又被加回来了。 +我们知道这点是因为这个例子很简单,但是在实际的例子中发现这样的冲突也很容易,因为每一行都被移除而在另一边每一行又被加回来了。 默认情况下,Git 认为所有这些行都改动了,所以它不会合并文件。 默认合并策略可以带有参数,其中的几个正好是关于忽略空白改动的。 -如果你看到在一次合并中有大量关于空白的问题,你可以直接中止它并重做一次, -这次使用 `-Xignore-all-space` 或 `-Xignore-space-change` 选项。 +如果你看到在一次合并中有大量关于空白的问题,你可以直接中止它并重做一次,这次使用 `-Xignore-all-space` 或 `-Xignore-space-change` 选项。 第一个选项在比较行时 *完全忽略* 空白修改,第二个选项将一个空白符与多个连续的空白字符视作等价的。 [source,console] @@ -228,11 +223,10 @@ index 36c06c8,e85207e..0000000 实际上,这比使用 `ignore-space-change` 选项要更好,因为在合并前真正地修复了空白修改而不是简单地忽略它们。 在使用 `ignore-space-change` 进行合并操作后,我们最终得到了有几行是 DOS 行尾的文件,从而使提交内容混乱了。 -如果你想要在最终提交前看一下我们这边与另一边之间实际的修改, -你可以使用 `git diff` 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。 +如果你想要在最终提交前看一下我们这边与另一边之间实际的修改,你可以使用 `git diff` 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。 让我们看看它们。 -要在合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么,可以运行 `git diff --ours` +要在合并前比较结果与在你的分支上的内容,换一句话说,看看合并引入了什么,可以运行 `git diff --ours` : [source,console] ---- @@ -256,8 +250,7 @@ index 36c06c8..44d0a25 100755 这里我们可以很容易地看到在我们的分支上发生了什么,在这次合并中我们实际引入到这个文件的改动,是修改了其中一行。 如果我们想要查看合并的结果与他们那边有什么不同,可以运行 `git diff --theirs`。 -在本例及后续的例子中,我们会使用 `-b` 来去除空白,因为我们将它与 Git 中的, -而不是我们清理过的 `hello.theirs.rb` 文件比较。 +在本例及后续的例子中,我们会使用 `-b` 来去除空白,因为我们将它与 Git 中的,而不是我们清理过的 `hello.theirs.rb` 文件比较。 [source,console] ---- @@ -369,8 +362,7 @@ hello() 如果想要重置标记并尝试再次解决它们的话这会很有用。 可以传递给 `--conflict` 参数 `diff3` 或 `merge`(默认选项)。 -如果传给它 `diff3`,Git 会使用一个略微不同版本的冲突标记: -不仅仅只给你 “ours” 和 “theirs” 版本,同时也会有 “base” 版本在中间来给你更多的上下文。 +如果传给它 `diff3`,Git 会使用一个略微不同版本的冲突标记:不仅仅只给你 “ours” 和 “theirs” 版本,同时也会有 “base” 版本在中间来给你更多的上下文。 [source,console] ---- @@ -414,8 +406,7 @@ $ git config --global merge.conflictstyle diff3 这可以帮助你得到那些对冲突有影响的上下文。 回顾一点历史来记起为什么两条线上的开发会触碰同一片代码有时会很有用。 -为了得到此次合并中包含的每一个分支的所有独立提交的列表, -我们可以使用之前在 <<> 学习的“三点”语法。 +为了得到此次合并中包含的每一个分支的所有独立提交的列表,我们可以使用之前在 <<> 学习的“三点”语法。 [source,console] ---- @@ -472,8 +463,7 @@ index 0399cd5,59727f0..0000000 ---- 这种叫作“组合式差异”的格式会在每一行给你两列数据。 -第一列为你显示 “ours” 分支与工作目录的文件区别(添加或删除), -第二列显示 “theirs” 分支与工作目录的拷贝区别。 +第一列为你显示 “ours” 分支与工作目录的文件区别(添加或删除),第二列显示 “theirs” 分支与工作目录的拷贝区别。 所以在上面的例子中可以看到 `<<<<<<<` 与 `>>>>>>>` 行在工作拷贝中但是并不在合并的任意一边中。 这很有意义,合并工具因为我们的上下文被困住了,它期望我们去移除它们。 @@ -500,12 +490,11 @@ index 0399cd5,59727f0..0000000 hello() ---- -这里显示出 “hola world” 在我们这边但不在工作拷贝中,那个 “hello mundo” 在他们那边但不在工作拷贝中, -最终 “hola mundo” 不在任何一边但是现在在工作拷贝中。在提交解决方案前这对审核很有用。 +这里显示出 “hola world” 在我们这边但不在工作拷贝中,那个 “hello mundo” 在他们那边但不在工作拷贝中,最终 “hola mundo” 不在任何一边但是现在在工作拷贝中。 +在提交解决方案前这对审核很有用。 也可以在合并后通过 `git log` 来获取相同信息,查看冲突是如何解决的。 -如果你对一个合并提交运行 `git show` 命令 Git 将会输出这种格式, -或者你也可以在 `git log -p`(默认情况下该命令只会展示还没有合并的补丁)命令之后加上 `--cc` 选项。 +如果你对一个合并提交运行 `git show` 命令 Git 将会输出这种格式,或者你也可以在 `git log -p`(默认情况下该命令只会展示还没有合并的补丁)命令之后加上 `--cc` 选项。 [source,console] ---- @@ -567,8 +556,7 @@ image::images/undomerge-reset.png[在 `git reset --hard HEAD~` 之后的历史] . 使工作目录看起来像索引。 这个方法的缺点是它会重写历史,在一个共享的仓库中这会造成问题的。 -查阅 <> 来了解更多可能发生的事情; -用简单的话说就是如果其他人已经有你将要重写的提交,你应当避免使用 `reset`。 +查阅 <> 来了解更多可能发生的事情;用简单的话说就是如果其他人已经有你将要重写的提交,你应当避免使用 `reset`。 如果有任何其他提交在合并之后创建了,那么这个方法也会无效;移动引用实际上会丢失那些改动。 [[_reverse_commit]] @@ -631,8 +619,7 @@ image::images/undomerge-revert3.png[在重新合并一个还原合并后的历 ===== 我们的或他们的偏好 首先,有另一种我们可以通过 “recursive” 合并模式做的有用工作。 -我们之前已经看到传递给 `-X` 的 `ignore-all-space` 与 `ignore-space-change` 选项, -但是我们也可以告诉 Git 当它看见一个冲突时直接选择一边。 +我们之前已经看到传递给 `-X` 的 `ignore-all-space` 与 `ignore-space-change` 选项,但是我们也可以告诉 Git 当它看见一个冲突时直接选择一边。 默认情况下,当 Git 看到两个分支合并中的冲突时,它会将合并冲突标记添加到你的代码中并标记文件为冲突状态来让你解决。 如果你希望 Git 简单地选择特定的一边并忽略另外一边而不是让你手动解决冲突,你可以传递给 `merge` 命令一个 `-Xours` 或 `-Xtheirs` 参数。 @@ -668,11 +655,9 @@ Merge made by the 'recursive' strategy. 在上例中,它并不会为 “hello mundo” 与 “hola world” 标记合并冲突,它只会简单地选取 “hola world”。 然而,在那个分支上所有其他非冲突的改动都可以被成功地合并入。 -这个选项也可以传递给我们之前看到的 `git merge-file` 命令, -通过运行类似 `git merge-file --ours` 的命令来合并单个文件。 +这个选项也可以传递给我们之前看到的 `git merge-file` 命令,通过运行类似 `git merge-file --ours` 的命令来合并单个文件。 -如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改, -有一个更严格的选项,它是 “ours” 合并 _策略_。 +如果想要做类似的事情但是甚至并不想让 Git 尝试合并另外一边的修改,有一个更严格的选项,它是 “ours” 合并 _策略_。 这与 “ours” recursive 合并 _选项_ 不同。 这本质上会做一次假的合并。 @@ -692,7 +677,6 @@ $ 当再次合并时从本质上欺骗 Git 认为那个分支已经合并过经常是很有用的。 例如,假设你有一个分叉的 `release` 分支并且在上面做了一些你想要在未来某个时候合并回 `master` 的工作。 与此同时 `master` 分支上的某些 bugfix 需要向后移植回 `release` 分支。 -你可以合并 bugfix 分支进入 `release` 分支同时也 `merge -s ours` 合并进入你的 `master` 分支 -(即使那个修复已经在那儿了)这样当你之后再次合并 `release` 分支时,就不会有来自 bugfix 的冲突。 +你可以合并 bugfix 分支进入 `release` 分支同时也 `merge -s ours` 合并进入你的 `master` 分支(即使那个修复已经在那儿了)这样当你之后再次合并 `release` 分支时,就不会有来自 bugfix 的冲突。 include::subtree-merges.asc[] diff --git a/book/07-git-tools/sections/bundling.asc b/book/07-git-tools/sections/bundling.asc index d81f2c93..54052724 100644 --- a/book/07-git-tools/sections/bundling.asc +++ b/book/07-git-tools/sections/bundling.asc @@ -8,12 +8,10 @@ Git 可以将它的数据“打包”到一个文件中。 有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。 可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。 可能你的无线、有线网卡坏掉了。 -可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人, -却不希望通过 `format-patch` 的方式传输 40 个提交。 +可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 `format-patch` 的方式传输 40 个提交。 这些情况下 `git bundle` 就会很有用。 -`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件, -你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。 +`bundle` 命令会将 `git push` 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。 来看看一个简单的例子。 假设你有一个包含两个提交的仓库: @@ -34,8 +32,7 @@ Date: Wed Mar 10 07:34:01 2010 -0800 first commit ---- -如果你想把这个仓库发送给其他人但你没有其他仓库的权限,或者就是懒得新建一个仓库, -你就可以用 `git bundle create` 命令来打包。 +如果你想把这个仓库发送给其他人但你没有其他仓库的权限,或者就是懒得新建一个仓库,你就可以用 `git bundle create` 命令来打包。 [source,console] ---- @@ -67,8 +64,7 @@ $ git log --oneline b1ec324 first commit ---- -如果你在打包时没有包含 HEAD 引用,你还需要在命令后指定一个 `-b master` 或者其他被引入的分支, -否则 Git 不知道应该检出哪一个分支。 +如果你在打包时没有包含 HEAD 引用,你还需要在命令后指定一个 `-b master` 或者其他被引入的分支,否则 Git 不知道应该检出哪一个分支。 现在假设你提交了 3 个修订,并且要用邮件或者U盘将新的提交放在一个包里传回去。 @@ -88,8 +84,7 @@ b1ec324 first commit 为了实现这个目标,你需要计算出差别。 就像我们在 <> 介绍的,你有很多种方式去指明一个提交区间。 -我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法 -来获取那 3 个在我们的 `master` 分支而不在原始仓库中的提交。 +我们可以使用 `origin/master..master` 或者 `master ^origin/master` 之类的方法来获取那 3 个在我们的 `master` 分支而不在原始仓库中的提交。 你可以用 `log` 命令来测试。 [source,console] @@ -114,8 +109,7 @@ Total 9 (delta 0), reused 0 (delta 0) ---- 现在在我们的目录下会有一个 `commits.bundle` 文件。 -如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中, -即使在这期间已经有其他的工作提交到这个仓库中。 +如果我们把这个文件发送给我们的合作者,她可以将这个文件导入到原始的仓库中,即使在这期间已经有其他的工作提交到这个仓库中。 当她拿到这个包时,她可以在导入到仓库之前查看这个包里包含了什么内容。 `bundle verify` 命令可以检查这个文件是否是一个合法的 Git 包,是否拥有共同的祖先来导入。 @@ -130,8 +124,8 @@ The bundle requires these 1 ref ../commits.bundle is okay ---- -如果打包工具仅仅把最后两个提交打包,而不是三个,原始的仓库是无法导入这个包的, -因为这个包缺失了必要的提交记录。这时候 `verify` 的输出类似: +如果打包工具仅仅把最后两个提交打包,而不是三个,原始的仓库是无法导入这个包的,因为这个包缺失了必要的提交记录。 +这时候 `verify` 的输出类似: [source,console] ---- diff --git a/book/07-git-tools/sections/credentials.asc b/book/07-git-tools/sections/credentials.asc index 1d5be269..09c0168f 100644 --- a/book/07-git-tools/sections/credentials.asc +++ b/book/07-git-tools/sections/credentials.asc @@ -19,11 +19,12 @@ 这种方式的缺点是你的密码是用明文的方式存放在你的 home 目录下。 * 如果你使用的是 Mac,Git 还有一种 “osxkeychain” 模式,它会将凭证缓存到你系统用户的钥匙串中。 这种方式将凭证存放在磁盘中,并且永不过期,但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的。 -* 如果你使用的是 Windows,你可以安装一个叫做 “Git Credential Manager for Windows” 的辅助工具。 +* 如果你使用的是 Windows,你可以在安装 https://gitforwindows.org/[Git for Windows^] 时启用 *Git Credential Manager* 功能,也可以单独安装 https://github.com/git-ecosystem/git-credential-manager/releases/latest[最新版的 GCM^] 作为独立服务。 这和上面说的 “osxkeychain” 十分类似,但是是使用 Windows Credential Store 来控制敏感信息。 - 可以在 https://github.com/Microsoft/Git-Credential-Manager-for-Windows[] 下载。 + 它还可以为 WSL1 或 WSL2 提供凭证服务。 + 更多信息参见 https://github.com/git-ecosystem/git-credential-manager#readme[GCM Install Instructions^] 。 -你可以设置 Git 的配置来选择上述的一种方式 +你可以设置 Git 的配置来选择上述的一种方式: [source,console] ---- @@ -112,14 +113,10 @@ password=s3cre7 对于 `store` 和 `erase` 两个行为是不需要返回数据的(Git 也会忽略掉)。 然而对于 `get`,Git 对辅助工具的返回信息十分感兴趣。 -如果辅助工具并不知道任何有用的信息,它就会直接退出而没有任何输出,但如果知道的话, -它就会在已存储信息的基础上扩充所提供的信息。 +如果辅助工具并不知道任何有用的信息,它就会直接退出而没有任何输出,但如果知道的话,它就会在已存储信息的基础上扩充所提供的信息。 它的输出可看做一系列赋值语句,提供的任何内容都会取代 Git 已知的内容。 -如果辅助工具没有任何有用的信息,它可以直接退出而不需要输出任何东西,但如果它有这些信息,它在提供的信息后面增加它所拥有的信息。 -这些输出会被视为一系列的赋值语句;每一个提供的数据都会将 Git 已有的数据替换掉。 - -这有一个和上面一样的例子,但是跳过了 git-credential 这一步,直接到 git-credential-store: +这有一个和上面一样的例子,但是跳过了 `git-credential` 这一步,直接到 `git-credential-store`: [source,console] ---- diff --git a/book/07-git-tools/sections/debugging.asc b/book/07-git-tools/sections/debugging.asc index 2a2c88c4..2160012a 100644 --- a/book/07-git-tools/sections/debugging.asc +++ b/book/07-git-tools/sections/debugging.asc @@ -10,8 +10,7 @@ 它能显示任何文件中每行最后一次修改的提交记录。 所以,如果你在代码中看到一个有 bug 的方法,你可以使用 `git blame` 标注这个文件,查看哪一次提交引入了这行。 -以下示例用 `git blame` 确定了 Linux 内核源码顶层的 `Makefile` 中每一行分别来自哪个提交和提交者, -此外用 `-L` 选项还可以将标注的输出限制为该文件中的第 69 行到第 82 行。 +以下示例用 `git blame` 确定了 Linux 内核源码顶层的 `Makefile` 中每一行分别来自哪个提交和提交者,此外用 `-L` 选项还可以将标注的输出限制为该文件中的第 69 行到第 82 行。 [source,console] ---- @@ -41,8 +40,7 @@ b8b0618cf6fab (Cheng Renquan 2009-05-26 16:03:07 +0800 70) KBUILD_VERBOSE = $ 另一件比较酷的事情是 Git 不会显式地记录文件的重命名。 它会记录快照,然后在事后尝试计算出重命名的动作。 这其中有一个很有意思的特性就是你可以让 Git 找出所有的代码移动。 -如果你在 `git blame` 后面加上一个 `-C`,Git 会分析你正在标注的文件, -并且尝试找出文件中从别的地方复制过来的代码片段的原始出处。 +如果你在 `git blame` 后面加上一个 `-C`,Git 会分析你正在标注的文件,并且尝试找出文件中从别的地方复制过来的代码片段的原始出处。 比如,你将 `GITServerHandler.m` 这个文件拆分为数个文件,其中一个文件是 `GITPackUpload.m`。 对 `GITPackUpload.m` 执行带 `-C` 参数的 blame 命令,你就可以看到代码块的原始出处: @@ -72,12 +70,10 @@ ad11ac80 GITPackUpload.m (Scott 2009-03-24 150) ==== 二分查找 当你知道问题是在哪里引入的情况下文件标注可以帮助你查找问题。 -如果你不知道哪里出了问题,并且自从上次可以正常运行到现在已经有数十个或者上百个提交, -这个时候你可以使用 `git bisect` 来帮助查找。 +如果你不知道哪里出了问题,并且自从上次可以正常运行到现在已经有数十个或者上百个提交,这个时候你可以使用 `git bisect` 来帮助查找。 `bisect` 命令会对你的提交历史进行二分查找来帮助你尽快找到是哪一个提交引入了问题。 -假设你刚刚在线上环境部署了你的代码,接着收到一些 bug 反馈, -但这些 bug 在你之前的开发环境里没有出现过,这让你百思不得其解。 +假设你刚刚在线上环境部署了你的代码,接着收到一些 bug 反馈,但这些 bug 在你之前的开发环境里没有出现过,这让你百思不得其解。 你重新查看了你的代码,发现这个问题是可以被重现的,但是你不知道哪里出了问题。 你可以用 *二分法* 来找到这个问题。 首先执行 `git bisect start` 来启动,接着执行 `git bisect bad` 来告诉系统当前你所在的提交是有问题的。 @@ -131,8 +127,7 @@ Date: Tue Jan 27 14:48:32 2009 -0800 f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M config ---- -当你完成这些操作之后,你应该执行 `git bisect reset` 重置你的 HEAD 指针到最开始的位置, -否则你会停留在一个奇怪的状态: +当你完成这些操作之后,你应该执行 `git bisect reset` 重置你的 HEAD 指针到最开始的位置,否则你会停留在一个奇怪的状态: [source,console] ---- diff --git a/book/07-git-tools/sections/interactive-staging.asc b/book/07-git-tools/sections/interactive-staging.asc index fd8742ca..6e3a9a5e 100644 --- a/book/07-git-tools/sections/interactive-staging.asc +++ b/book/07-git-tools/sections/interactive-staging.asc @@ -181,8 +181,7 @@ e - manually edit the current hunk ? - print help ---- -通常情况下可以输入 `y` 或 `n` 来选择是否要暂存每一个区块, -当然,暂存特定文件中的所有部分或为之后的选择跳过一个区块也是非常有用的。 +通常情况下可以输入 `y` 或 `n` 来选择是否要暂存每一个区块,当然,暂存特定文件中的所有部分或为之后的选择跳过一个区块也是非常有用的。 如果你只暂存文件的一部分,状态输出可能会像下面这样: [source,console] @@ -201,6 +200,5 @@ What now> 1 也可以不必在交互式添加模式中做部分文件暂存——可以在命令行中使用 `git add -p` 或 `git add --patch` 来启动同样的脚本。 -更进一步地,可以使用 `git reset --patch` 命令的补丁模式来部分重置文件, -通过 `git checkout --patch` 命令来部分检出文件与 `git stash save --patch` 命令来部分暂存文件。 +更进一步地,可以使用 `git reset --patch` 命令的补丁模式来部分重置文件,通过 `git checkout --patch` 命令来部分检出文件与 `git stash save --patch` 命令来部分暂存文件。 我们将会在接触这些命令的高级使用方法时了解更多详细信息。 diff --git a/book/07-git-tools/sections/replace.asc b/book/07-git-tools/sections/replace.asc index 9558aa78..5ee0e35a 100644 --- a/book/07-git-tools/sections/replace.asc +++ b/book/07-git-tools/sections/replace.asc @@ -1,20 +1,17 @@ [[_replace]] === 替换 -我们之前强调过,Git 对象数据库中的对象是不可改变的, -然而 Git 提供了一种有趣的方式来用其他对象 *假装* 替换数据库中的 Git 对象。 +我们之前强调过,Git 对象数据库中的对象是不可改变的,然而 Git 提供了一种有趣的方式来用其他对象 *假装* 替换数据库中的 Git 对象。 `replace` 命令可以让你在 Git 中指定 *某个对象* 并告诉 Git:“每次遇到这个 Git 对象时,假装它是 *其它对象*”。 在你用一个不同的提交替换历史中的一个提交而不想以 `git filter-branch` 之类的方式重建完整的历史时,这会非常有用。 -例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史, -短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。 +例如,你有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。 你可以通过用新仓库中最早的提交“替换”老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。 这意味着你不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA-1 值),你可以加入他们。 让我们来试试吧。 -首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库, -然后我们将看到如何在不更改仓库 SHA-1 值的情况下通过 `replace` 命令来合并他们。 +首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库,然后我们将看到如何在不更改仓库 SHA-1 值的情况下通过 `replace` 命令来合并他们。 我们将使用一个拥有 5 个提交的简单仓库: @@ -32,7 +29,8 @@ c1822cf first commit 第一个到第四个提交的作为第一个历史版本。 第四、第五个提交的作为最近的第二个历史版本。 -image::images/replace1.png[] +.Git 历史示例 +image::images/replace1.png[Git 历史示例] 创建历史版本的历史很容易,我们可以只将一个历史中的分支推送到一个新的远程仓库的 `master` 分支。 @@ -47,7 +45,8 @@ c6e1e95 (history) fourth commit c1822cf first commit ---- -image::images/replace2.png[] +.创建新的 `history` 分支 +image::images/replace2.png[创建新的 `history` 分支] 现在我们可以把这个新的 `history` 分支推送到我们新仓库的 `master` 分支: @@ -79,8 +78,8 @@ c6e1e95 (history) fourth commit c1822cf first commit ---- -在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。 -这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么,因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。 +在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么, +因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。 为了这么做,我们需要选择一个点去拆分,对于我们而言是第三个提交(SHA 是 `9c68fdc`)。 因此我们的提交将基于此提交树。我们可以使用 `commit-tree` 命令来创建基础提交, @@ -94,12 +93,14 @@ $ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree} [NOTE] ===== -`commit-tree` 命令属于底层指令。有许多指令并非直接使用,而是被 **其他的** Git 命令用来做更小一些的工作。 +`commit-tree` 命令属于底层(plumbing)指令。 +有许多指令并非直接使用,而是被 **其他的** Git 命令用来做更小一些的工作。 有时当我们做一些像这样的奇怪事情时,它们允许我们做一些不适用于日常使用但真正底层的东西。 更多关于底层命令的内容请参见 <>。 ===== -image::images/replace3.png[] +.使用 `commit-tree` 创建基础提交 +image::images/replace3.png[使用 `commit-tree` 创建基础提交] 现在我们已经有一个基础提交了,我们可以通过 `git rebase --onto` 命令来将剩余的历史变基到基础提交之上。 `--onto` 参数是刚才 `commit-tree` 命令返回的 SHA-1 值,变基点会成为第三个提交(我们想留下的第一个提交的父提交,`9c68fdc`): @@ -112,7 +113,8 @@ Applying: fourth commit Applying: fifth commit ---- -image::images/replace4.png[] +.将提交历史变基到基础提交之上 +image::images/replace4.png[将提交历史变基到基础提交之上] 我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。 我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。 @@ -172,10 +174,10 @@ e146b5f fifth commit c1822cf first commit ---- -很酷,是不是?不用改变上游的 SHA-1 我们就能用一个提交来替换历史中的所有不同的提交, -并且所有的工具(`bisect`,`blame` 等)也都奏效。 +很酷,是不是?不用改变上游的 SHA-1 我们就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(`bisect`,`blame` 等)也都奏效。 -image::images/replace5.png[] +.使用 `git replace` 组合提交 +image::images/replace5.png[使用 `git replace` 组合提交] 有趣的是,即使是使用了 `c6e1e95` 提交数据来进行替换,它的 SHA-1 仍显示为 `81a708d`。 即使你运行了 `cat-file` 命令,它仍会显示你替换的数据: diff --git a/book/07-git-tools/sections/rerere.asc b/book/07-git-tools/sections/rerere.asc index deb9475f..891d7262 100644 --- a/book/07-git-tools/sections/rerere.asc +++ b/book/07-git-tools/sections/rerere.asc @@ -2,8 +2,7 @@ === Rerere `git rerere` 功能是一个隐藏的功能。 -正如它的名字“重用记录的解决方案(reuse recorded resolution)”所示,它允许你让 Git 记住解决一个块冲突的方法, -这样在下一次看到相同冲突时,Git 可以为你自动地解决它。 +正如它的名字“重用记录的解决方案(reuse recorded resolution)”所示,它允许你让 Git 记住解决一个块冲突的方法,这样在下一次看到相同冲突时,Git 可以为你自动地解决它。 有几种情形下这个功能会非常有用。 在文档中提到的一个例子是想要保证一个长期分支会干净地合并,但是又不想要一串中间的合并提交弄乱你的提交历史。 @@ -39,7 +38,8 @@ end 在一个分支中修改单词 “hello” 为 “hola”,然后在另一个分支中修改 “world” 为 “mundo”,就像之前一样。 -image::images/rerere1.png[] +.两个分支对同一文件的相同部分作出了不同的修改 +image::images/rerere1.png[两个分支对同一文件的相同部分作出了不同的修改] 当合并两个分支到一起时,我们将会得到一个合并冲突: @@ -144,7 +144,8 @@ Recorded resolution for 'hello.rb'. 可以看到它 "Recorded resolution for FILE"。 -image::images/rerere2.png[] +.FILE 的已记录解决方案 +image::images/rerere2.png[FILE 的已记录解决方案] 现在,让我们撤消那个合并然后将它变基到 `master` 分支顶部来替代它。 可以通过使用之前在 <> 看到的 `git reset` 来回滚分支。 @@ -206,7 +207,8 @@ index a440db6,54336ba..0000000 end ---- -image::images/rerere3.png[] +.已通过先前的解决方案自动解决合并冲突 +image::images/rerere3.png[已通过先前的解决方案自动解决合并冲突] 也可以通过 `git checkout` 命令重新恢复到冲突时候的文件状态: @@ -250,5 +252,4 @@ $ git rebase --continue Applying: i18n one word ---- -所以,如果做了很多次重新合并,或者想要一个主题分支始终与你的 `master` 分支保持最新但却不想要一大堆合并, -或者经常变基,打开 `rerere` 功能可以帮助你的生活变得更美好。 +所以,如果做了很多次重新合并,或者想要一个主题分支始终与你的 `master` 分支保持最新但却不想要一大堆合并,或者经常变基,打开 `rerere` 功能可以帮助你的生活变得更美好。 diff --git a/book/07-git-tools/sections/reset.asc b/book/07-git-tools/sections/reset.asc index 944fbfbf..79b3d069 100644 --- a/book/07-git-tools/sections/reset.asc +++ b/book/07-git-tools/sections/reset.asc @@ -46,8 +46,7 @@ $ git ls-tree -r HEAD 040000 tree 99f1a6d12cb4b6f19... lib ---- -Git 的 `cat-file` 和 `ls-tree` 是底层命令,它们一般用于底层工作,在日常工作中并不使用。 -不过它们能帮助我们了解到底发生了什么。 +Git 的 `cat-file` 和 `ls-tree` 是底层命令,它们一般用于底层工作,在日常工作中并不使用。不过它们能帮助我们了解到底发生了什么。 [[_the_index]] ===== 索引 @@ -93,24 +92,27 @@ $ tree 经典的 Git 工作流程是通过操纵这三个区域来以更加连续的状态记录项目快照的。 -image::images/reset-workflow.png[] +.Git 的典型工作流 +image::images/reset-workflow.png[Git 的典型工作流] 让我们来可视化这个过程:假设我们进入到一个新目录,其中有一个文件。 我们称其为该文件的 *v1* 版本,将它标记为蓝色。 现在运行 `git init`,这会创建一个 Git 仓库,其中的 HEAD 引用指向未创建的 `master` 分支。 -image::images/reset-ex1.png[] +.包含工作区未暂存文件的新建 Git 仓库 +image::images/reset-ex1.png[包含工作区未暂存文件的新建 Git 仓库] 此时,只有工作目录有内容。 现在我们想要提交这个文件,所以用 `git add` 来获取工作目录中的内容,并将其复制到索引中。 -image::images/reset-ex2.png[] +.执行 `git add` 后文件被复制到暂存区 +image::images/reset-ex2.png[执行 `git add` 后文件被复制到暂存区] -接着运行 `git commit`,它会取得索引中的内容并将它保存为一个永久的快照, -然后创建一个指向该快照的提交对象,最后更新 `master` 来指向本次提交。 +接着运行 `git commit`,它会取得索引中的内容并将它保存为一个永久的快照,然后创建一个指向该快照的提交对象,最后更新 `master` 来指向本次提交。 -image::images/reset-ex3.png[] +.`git commit` 步骤 +image::images/reset-ex3.png[`git commit` 步骤] 此时如果我们运行 `git status`,会发现没有任何改动,因为现在三棵树完全相同。 @@ -118,25 +120,25 @@ image::images/reset-ex3.png[] 我们将会经历同样的过程;首先在工作目录中修改文件。 我们称其为该文件的 *v2* 版本,并将它标记为红色。 -image::images/reset-ex4.png[] +.包含工作区已修改文件中的 Git 仓库 +image::images/reset-ex4.png[包含工作区已修改文件中的 Git 仓库] -如果现在运行 `git status`,我们会看到文件显示在 “Changes not staged for commit” -下面并被标记为红色,因为该条目在索引与工作目录之间存在不同。 +如果现在运行 `git status`,我们会看到文件显示在 “Changes not staged for commit” 下面并被标记为红色,因为该条目在索引与工作目录之间存在不同。 接着我们运行 `git add` 来将它暂存到索引中。 -image::images/reset-ex5.png[] +.将变更暂存至暂存区 +image::images/reset-ex5.png[将变更暂存至暂存区] -此时,由于索引和 HEAD 不同,若运行 `git status` 的话就会看到 “Changes to be committed” 下的该文件变为绿色 -——也就是说,现在预期的下一次提交与上一次提交不同。 +此时,由于索引和 HEAD 不同,若运行 `git status` 的话就会看到 “Changes to be committed” 下的该文件变为绿色——也就是说,现在预期的下一次提交与上一次提交不同。 最后,我们运行 `git commit` 来完成提交。 -image::images/reset-ex6.png[] +.包含已修改文件的 `git commit` 步骤 +image::images/reset-ex6.png[包含已修改文件的 `git commit` 步骤] 现在运行 `git status` 会没有输出,因为三棵树又变得相同了。 切换分支或克隆的过程也类似。 -当检出一个分支时,它会修改 *HEAD* 指向新的分支引用,将 *索引* 填充为该次提交的快照, -然后将 *索引* 的内容复制到 *工作目录* 中。 +当检出一个分支时,它会修改 *HEAD* 指向新的分支引用,将 *索引* 填充为该次提交的快照,然后将 *索引* 的内容复制到 *工作目录* 中。 ==== 重置的作用 @@ -145,7 +147,8 @@ image::images/reset-ex6.png[] 为了演示这些例子,假设我们再次修改了 `file.txt` 文件并第三次提交它。 现在的历史看起来是这样的: -image::images/reset-start.png[] +.包含三个提交的 Git 仓库 +image::images/reset-start.png[包含三个提交的 Git 仓库] 让我们跟着 `reset` 看看它都做了什么。 它以一种简单可预见的方式直接操纵这三棵树。 @@ -155,10 +158,10 @@ image::images/reset-start.png[] `reset` 做的第一件事是移动 HEAD 的指向。 这与改变 HEAD 自身不同(`checkout` 所做的);`reset` 移动 HEAD 指向的分支。 -这意味着如果 HEAD 设置为 `master` 分支(例如,你正在 `master` 分支上), -运行 `git reset 9e5e6a4` 将会使 `master` 指向 `9e5e6a4`。 +这意味着如果 HEAD 设置为 `master` 分支(例如,你正在 `master` 分支上),运行 `git reset 9e5e6a4` 将会使 `master` 指向 `9e5e6a4`。 -image::images/reset-soft.png[] +.软重置 +image::images/reset-soft.png[软重置] 无论你调用了何种形式的带有一个提交的 `reset`,它首先都会尝试这样做。 使用 `reset --soft`,它将仅仅停在那儿。 @@ -174,7 +177,8 @@ image::images/reset-soft.png[] 接下来,`reset` 会用 HEAD 指向的当前快照的内容来更新索引。 -image::images/reset-mixed.png[] +.混合重置 +image::images/reset-mixed.png[混合重置] 如果指定 `--mixed` 选项,`reset` 将会在这时停止。 这也是默认行为,所以如果没有指定任何选项(在本例中只是 `git reset HEAD~`),这就是命令将会停止的地方。 @@ -187,15 +191,15 @@ image::images/reset-mixed.png[] `reset` 要做的第三件事情就是让工作目录看起来像索引。 如果使用 `--hard` 选项,它将会继续这一步。 -image::images/reset-hard.png[] +.硬重置 +image::images/reset-hard.png[硬重置] 现在让我们回想一下刚才发生的事情。 你撤销了最后的提交、`git add` 和 `git commit` 命令 *以及* 工作目录中的所有工作。 必须注意,`--hard` 标记是 `reset` 命令唯一的危险用法,它也是 Git 会真正地销毁数据的仅有的几个操作之一。 其他任何形式的 `reset` 调用都可以轻松撤消,但是 `--hard` 选项不能,因为它强制覆盖了工作目录中的文件。 -在这种特殊情况下,我们的 Git 数据库中的一个提交内还留有该文件的 *v3* 版本, -我们可以通过 `reflog` 来找回它。但是若该文件还未提交,Git 仍会覆盖它从而导致无法恢复。 +在这种特殊情况下,我们的 Git 数据库中的一个提交内还留有该文件的 *v3* 版本,我们可以通过 `reflog` 来找回它。但是若该文件还未提交,Git 仍会覆盖它从而导致无法恢复。 ===== 回顾 @@ -213,33 +217,32 @@ image::images/reset-hard.png[] 不过索引和工作目录 _可以部分更新_,所以重置会继续进行第 2、3 步。 现在,假如我们运行 `git reset file.txt` -(这其实是 `git reset --mixed HEAD file.txt` 的简写形式,因为你既没有指定一个提交的 -SHA-1 或分支,也没有指定 `--soft` 或 `--hard`),它会: +(这其实是 `git reset --mixed HEAD file.txt` 的简写形式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 `--soft` 或 `--hard`),它会: 1. 移动 HEAD 分支的指向 _(已跳过)_ 2. 让索引看起来像 HEAD _(到此处停止)_ 所以它本质上只是将 `file.txt` 从 HEAD 复制到索引中。 -image::images/reset-path1.png[] +.带路径的混合重置 +image::images/reset-path1.png[带路径的混合重置] 它还有 _取消暂存文件_ 的实际效果。 如果我们查看该命令的示意图,然后再想想 `git add` 所做的事,就会发现它们正好相反。 -image::images/reset-path2.png[] +.将文件暂存至暂存区 +image::images/reset-path2.png[将文件暂存至暂存区] -这就是为什么 `git status` 命令的输出会建议运行此命令来取消暂存一个文件。 -(查看 <> 来了解更多。) +这就是为什么 `git status` 命令的输出会建议运行此命令来取消暂存一个文件(查看 <> 来了解更多)。 我们可以不让 Git 从 HEAD 拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本。 我们只需运行类似于 `git reset eb43bf file.txt` 的命令即可。 -image::images/reset-path3.png[] +.带路径且指定提交的软重置 +image::images/reset-path3.png[带路径且指定提交的软重置] -它其实做了同样的事情,也就是把工作目录中的文件恢复到 *v1* 版本,运行 `git add` 添加它, -然后再将它恢复到 *v3* 版本(只是不用真的过一遍这些步骤)。 -如果我们现在运行 `git commit`,它就会记录一条“将该文件恢复到 *v1* 版本”的更改, -尽管我们并未在工作目录中真正地再次拥有它。 +它其实做了同样的事情,也就是把工作目录中的文件恢复到 *v1* 版本,运行 `git add` 添加它,然后再将它恢复到 *v3* 版本(只是不用真的过一遍这些步骤)。 +如果我们现在运行 `git commit`,它就会记录一条“将该文件恢复到 *v1* 版本”的更改,尽管我们并未在工作目录中真正地再次拥有它。 还有一点同 `git add` 一样,就是 `reset` 命令也可以接受一个 `--patch` 选项来一块一块地取消暂存的内容。 这样你就可以根据选择来取消暂存或恢复内容了。 @@ -255,18 +258,20 @@ image::images/reset-path3.png[] 假设你有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提交再次修改了第一个文件。 由于第二次提交是一个未完成的工作,因此你想要压缩它。 -image::images/reset-squash-r1.png[] +.Git 仓库 +image::images/reset-squash-r1.png[Git 仓库] 那么可以运行 `git reset --soft HEAD~2` 来将 HEAD 分支移动到一个旧一点的提交上(即你想要保留的最近的提交): -image::images/reset-squash-r2.png[] +.通过软重置移动 HEAD +image::images/reset-squash-r2.png[通过软重置移动 HEAD] 然后只需再次运行 `git commit`: -image::images/reset-squash-r3.png[] +.包含压缩提交的 Git 仓库 +image::images/reset-squash-r3.png[包含压缩提交的 Git 仓库] -现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 `file-a.txt` 的提交, -接着第二个提交将 `file-a.txt` 修改成了 v3 版并增加了 `file-b.txt`。 +现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 `file-a.txt` 的提交,接着第二个提交将 `file-a.txt` 修改成了 v3 版并增加了 `file-b.txt`。 包含 v2 版本的文件已经不在历史中了。 ==== 检出 @@ -293,14 +298,14 @@ image::images/reset-squash-r3.png[] 所以,虽然在这两种情况下我们都移动 HEAD 使其指向了提交 A,但 _做法_ 是非常不同的。 `reset` 会移动 HEAD 分支的指向,而 `checkout` 则移动 HEAD 自身。 -image::images/reset-checkout.png[] +.`git checkout` 与 `git reset` +image::images/reset-checkout.png[`git checkout` 与 `git reset`] ===== 带路径 运行 `checkout` 的另一种方式就是指定一个文件路径,这会像 `reset` 一样不会移动 HEAD。 它就像 `git reset [branch] file` 那样用该次提交中的那个文件来更新索引,但是它也会覆盖工作目录中对应的文件。 -它就像是 `git reset --hard [branch] file`(如果 `reset` 允许你这样运行的话), -这样对工作目录并不安全,它也不会移动 HEAD。 +它就像是 `git reset --hard [branch] file`(如果 `reset` 允许你这样运行的话),这样对工作目录并不安全,它也不会移动 HEAD。 此外,同 `git reset` 和 `git add` 一样,`checkout` 也接受一个 `--patch` 选项,允许你根据选择一块一块地恢复文件内容。 diff --git a/book/07-git-tools/sections/revision-selection.asc b/book/07-git-tools/sections/revision-selection.asc index 7157fdc4..f3db7c82 100644 --- a/book/07-git-tools/sections/revision-selection.asc +++ b/book/07-git-tools/sections/revision-selection.asc @@ -6,14 +6,12 @@ Git 能够以多种方式来指定单个提交、一组提交、或者一定范 ==== 单个修订版本 -你可以通过任意一个提交的 40 个字符的完整 SHA-1 散列值来指定它, -不过还有很多更人性化的方式来做同样的事情。本节将会介绍获取单个提交的多种方法。 +你可以通过任意一个提交的 40 个字符的完整 SHA-1 散列值来指定它,不过还有很多更人性化的方式来做同样的事情。 +本节将会介绍获取单个提交的多种方法。 ==== 简短的 SHA-1 -Git 十分智能,你只需要提供 SHA-1 的前几个字符就可以获得对应的那次提交, -当然你提供的 SHA-1 字符数量不得少于 4 个,并且没有歧义——也就是说, -当前对象数据库中没有其它对象以这段 SHA-1 开头。 +Git 十分智能,你只需要提供 SHA-1 的前几个字符就可以获得对应的那次提交,当然你提供的 SHA-1 字符数量不得少于 4 个,并且没有歧义——也就是说,当前对象数据库中没有其它对象以这段 SHA-1 开头。 例如,要查看你知道其中添加了某个功能的提交,首先运行 `git log` 命令来定位该提交: @@ -51,8 +49,7 @@ $ git show 1c002d ---- Git 可以为 SHA-1 值生成出简短且唯一的缩写。 -如果你在 `git log` 后加上 `--abbrev-commit` 参数,输出结果里就会显示简短且唯一的值; -默认使用七个字符,不过有时为了避免 SHA-1 的歧义,会增加字符数: +如果你在 `git log` 后加上 `--abbrev-commit` 参数,输出结果里就会显示简短且唯一的值;默认使用七个字符,不过有时为了避免 SHA-1 的歧义,会增加字符数: [source,console] ---- @@ -63,8 +60,7 @@ a11bef0 first commit ---- 通常 8 到 10 个字符就已经足够在一个项目中避免 SHA-1 的歧义。 -例如,到 2019 年 2 月为止,Linux 内核这个相当大的 Git 项目, -其对象数据库中有超过 875,000 个提交,包含七百万个对象,也只需要前 12 个字符就能保证唯一性。 +例如,到 2019 年 2 月为止,Linux 内核这个相当大的 Git 项目,其对象数据库中有超过 875,000 个提交,包含七百万个对象,也只需要前 12 个字符就能保证唯一性。 [NOTE] .关于 SHA-1 的简短说明 @@ -73,30 +69,28 @@ a11bef0 first commit 许多人觉得他们的仓库里有可能出现两个不同的对象其 SHA-1 值相同。 然后呢? -如果你真的向仓库里提交了一个对象,它跟之前的某个 *不同* 对象的 SHA-1 值相同, -Git 会发现该对象的散列值已经存在于仓库里了,于是就会认为该对象被写入,然后直接使用它。 +如果你真的向仓库里提交了一个对象,它跟之前的某个 *不同* 对象的 SHA-1 值相同,Git 会发现该对象的散列值已经存在于仓库里了,于是就会认为该对象被写入,然后直接使用它。 如果之后你想检出那个对象时,你将得到先前那个对象的数据。 但是这种情况发生的概率十分渺小。 SHA-1 摘要长度是 20 字节,也就是 160 位。 -2^80 个随机哈希对象才有 50% 的概率出现一次冲突 -(计算冲突机率的公式是 `p = (n(n-1)/2) * (1/2^160))` )。 -2^80 是 1.2 x 10^24,也就是一亿亿亿,这是地球上沙粒总数的 1200 倍。 +2^80 个随机哈希对象才有 50% 的概率出现一次冲突(计算冲突机率的公式是 `p = (n(n-1)/2) * (1/2^160))` )。 +2^80 是 1.2 x 10^24,也就是一亿亿亿。 +这是地球上沙粒总数的 1200 倍。 举例说一下怎样才能产生一次 SHA-1 冲突。 -如果地球上 65 亿个人类都在编程,每人每秒都在产生等价于整个 Linux 内核历史(650 万个 Git 对象)的代码, -并将之提交到一个巨大的 Git 仓库里面,这样持续两年的时间才会产生足够的对象, -使其拥有 50% 的概率产生一次 SHA-1 对象冲突, +如果地球上 65 亿个人类都在编程,每人每秒都在产生等价于整个 Linux 内核历史(650 万个 Git 对象)的代码,并将之提交到一个巨大的 Git 仓库里面,这样持续两年的时间才会产生足够的对象,使其拥有 50% 的概率产生一次 SHA-1 对象冲突。 这比你编程团队的成员同一个晚上在互不相干的意外中被狼袭击并杀死的机率还要小。 + +只要你投入价值数千美元的算力,合成两个具有相同哈希值的文件是完全可能的,这已在 2017 年 2 月的 https://shattered.io/[^] 验证成功。 +Git 正逐步将 SHA256 作为默认的哈希算法,该算法对碰撞攻击具有强得多的防御能力,同时 Git 内部也已加入相关代码来规避此类攻击(尽管尚无法彻底消除该风险)。 ==== [[_branch_references]] ==== 分支引用 -引用特定提交的一种直接方法是,若它是一个分支的顶端的提交, -那么可以在任何需要引用该提交的 Git 命令中直接使用该分支的名称。 -例如,你想要查看一个分支的最后一次提交的对象,假设 `topic1` 分支指向提交 `ca82a6d...` , -那么以下的命令是等价的: +引用特定提交的一种直接方法是,若它是一个分支的顶端的提交,那么可以在任何需要引用该提交的 Git 命令中直接使用该分支的名称。 +例如,你想要查看一个分支的最后一次提交的对象,假设 `topic1` 分支指向提交 `ca82a6d...` ,那么以下的命令是等价的: [source,console] ---- @@ -104,12 +98,10 @@ $ git show ca82a6dff817ec66f44342007202690a93763949 $ git show topic1 ---- -如果你想知道某个分支指向哪个特定的 SHA-1,或者想看任何一个例子中被简写的 SHA-1, -你可以使用一个叫做 `rev-parse` 的 Git 探测工具。 -你可以在 <> 中查看更多关于探测工具的信息。 -简单来说,`rev-parse` 是为了底层操作而不是日常操作设计的。 +如果你想知道某个分支指向哪个特定的 SHA-1,或者想看任何一个例子中被简写的 SHA-1,你可以使用一个叫做 `rev-parse` 的 Git 探测工具。 +你可以在 <> 中查看更多关于探测工具的信息。简单来说,`rev-parse` 是为了底层操作而不是日常操作设计的。 不过,有时你想看 Git 现在到底处于什么状态时,它可能会很有用。 -你可以在你的分支上执行 `rev-parse` +你可以在你的分支上执行 `rev-parse` 。 [source,console] ---- @@ -120,10 +112,9 @@ ca82a6dff817ec66f44342007202690a93763949 [[_git_reflog]] ==== 引用日志 -当你在工作时, Git 会在后台保存一个引用日志(reflog), -引用日志记录了最近几个月你的 HEAD 和分支引用所指向的历史。 +当你在工作时, Git 会在后台保存一个引用日志(reflog),引用日志记录了最近几个月你的 HEAD 和分支引用所指向的历史。 -你可以使用 `git reflog` 来查看引用日志 +你可以使用 `git reflog` 来查看引用日志: [source,console] ---- @@ -139,7 +130,7 @@ d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy. 每当你的 HEAD 所指向的位置发生了变化,Git 就会将这个信息存储到引用日志这个历史记录里。 你也可以通过 reflog 数据来获取之前的提交历史。 -如果你想查看仓库中 HEAD 在五次前的所指向的提交,你可以使用 `@{n}` 来引用 reflog 中输出的提交记录。 +如果你想查看仓库中 HEAD 在五次前的所指向的提交,你可以使用 `@{n}` 来引用 reflog 中输出的提交记录: [source,console] ---- @@ -147,7 +138,7 @@ $ git show HEAD@{5} ---- 你同样可以使用这个语法来查看某个分支在一定时间前的位置。 -例如,查看你的 `master` 分支在昨天的时候指向了哪个提交,你可以输入 +例如,查看你的 `master` 分支在昨天的时候指向了哪个提交,你可以输入: [source,console] ---- @@ -181,14 +172,28 @@ Date: Thu Dec 11 15:08:43 2008 -0800 值得注意的是,引用日志只存在于本地仓库,它只是一个记录你在 *自己* 的仓库里做过什么的日志。 其他人拷贝的仓库里的引用日志不会和你的相同,而你新克隆一个仓库的时候,引用日志是空的,因为你在仓库里还没有操作。 -`git show HEAD@{2.months.ago}` 这条命令只有在你克隆了一个项目至少两个月时才会显示匹配的提交—— -如果你刚刚克隆了仓库,那么它将不会有任何结果返回。 +`git show HEAD@{2.months.ago}` 这条命令只有在你克隆了一个项目至少两个月时才会显示匹配的提交 —— 如果你刚刚克隆了仓库,那么它将不会有任何结果返回。 [TIP] .将引用日志想作 Git 版的 shell 历史记录 ==== -如果你有 UNIX 或者 Linux 的背景,不妨将引用日志想作 Git 版的 shell 历史记录, -重点在于仅与你和你的会话相关,而与他人无关。 +如果你有 UNIX 或者 Linux 的背景,不妨将引用日志想作 Git 版的 shell 历史记录,重点在于仅与你和你的会话相关,而与他人无关。 +==== + +[NOTE] +.在 PowerShell 中转义花括号 +==== + +在 PowerShell 中,花括号(例如 `{` 和 `}`)属于特殊字符,必须进行转义。 +你可以使用反引号 ` 对其进行转义,或者将提交引用用引号括起来: + +[source,console] +---- +$ git show HEAD@{0} # 无法工作 +$ git show HEAD@`{0`} # 可以 +$ git show "HEAD@{0}" # 可以 +---- + ==== ==== 祖先引用 @@ -240,8 +245,7 @@ $ git show "HEAD^" # 可以 ==== 你也可以在 `^` 后面添加一个数字来指明想要 *哪一个* 父提交——例如 `d921970^2` 代表 “d921970 的第二父提交” -这个语法只适用于合并的提交,因为合并提交会有多个父提交。 -合并提交的第一父提交是你合并时所在分支(通常为 `master`),而第二父提交是你所合并的分支(例如 `topic`): +这个语法只适用于合并的提交,因为合并提交会有多个父提交。合并提交的第一父提交是你合并时所在分支(通常为 `master`),而第二父提交是你所合并的分支(例如 `topic`): [source,console] ---- @@ -264,7 +268,7 @@ Date: Wed Dec 10 22:22:03 2008 +0000 同样是指向第一父提交,因此 `HEAD~` 和 `HEAD^` 是等价的。 而区别在于你在后面加数字的时候。 `HEAD~2` 代表“第一父提交的第一父提交”,也就是“祖父提交”——Git 会根据你指定的次数获取对应的第一父提交。 -例如,在之前的列出的提交历史中,`HEAD~3` 就是 +例如,在之前的列出的提交历史中,`HEAD~3` 就是: [source,console] ---- @@ -288,14 +292,13 @@ Date: Fri Nov 7 13:47:59 2008 -0500 ignore *.gem ---- -你也可以组合使用这两个语法——你可以通过 `HEAD~3^2` 来取得之前引用的第二父提交(假设它是一个合并提交)。 +你也可以组合使用这两个语法——你可以通过 `HEAD~3^2` 来取得之前引用的第二父提交(假设它是一个合并提交),以此类推。 [[_commit_ranges]] ==== 提交区间 你已经学会如何指定单次的提交,现在来看看如何指明一定区间的提交。 -当你有很多分支时,这对管理你的分支十分有用, -你可以用提交区间来解决“这个分支还有哪些提交尚未合并到主分支?”的问题 +当你有很多分支时,这对管理你的分支十分有用,你可以用提交区间来解决“这个分支还有哪些提交尚未合并到主分支?”的问题 ===== 双点 @@ -337,14 +340,13 @@ $ git log origin/master..HEAD ---- 这个命令会输出在你当前分支中而不在远程 `origin` 中的提交。 -如果你执行 `git push` 并且你的当前分支正在跟踪 `origin/master`,由 `git log origin/master..HEAD` -所输出的提交就是会被传输到远端服务器的提交。如果你留空了其中的一边, Git 会默认为 `HEAD`。 -例如, `git log origin/master..` 将会输出与之前例子相同的结果 —— Git 使用 HEAD 来代替留空的一边。 +如果你执行 `git push` 并且你的当前分支正在跟踪 `origin/master`,由 `git log origin/master..HEAD` 所输出的提交就是会被传输到远端服务器的提交。 +如果你留空了其中的一边, Git 会默认为 `HEAD`。 +例如, `git log origin/master..` 将会输出与之前例子相同的结果 —— Git 使用 `HEAD` 来代替留空的一边。 ===== 多点 -双点语法很好用,但有时候你可能需要两个以上的分支才能确定你所需要的修订, -比如查看哪些提交是被包含在某些分支中的一个,但是不在你当前的分支上。 +双点语法很好用,但有时候你可能需要两个以上的分支才能确定你所需要的修订,比如查看哪些提交是被包含在某些分支中的一个,但是不在你当前的分支上。 Git 允许你在任意引用前加上 `^` 字符或者 `--not` 来指明你不希望提交被包含其中的分支。 因此下列三个命令是等价的: diff --git a/book/07-git-tools/sections/rewriting-history.asc b/book/07-git-tools/sections/rewriting-history.asc index 0503c25f..feee85f3 100644 --- a/book/07-git-tools/sections/rewriting-history.asc +++ b/book/07-git-tools/sections/rewriting-history.asc @@ -3,10 +3,8 @@ 许多时候,在使用 Git 时,你可能想要修订提交历史。 Git 很棒的一点是它允许你在最后时刻做决定。 -你可以在将暂存区内容提交前决定哪些文件进入提交,可以通过 `git stash` 来决定不与某些内容工作, -也可以重写已经发生的提交就像它们以另一种方式发生的一样。 -这可能涉及改变提交的顺序,改变提交中的信息或修改文件,将提交压缩或是拆分, -或完全地移除提交——在将你的工作成果与他人共享之前。 +你可以在将暂存区内容提交前决定哪些文件进入提交,可以通过 `git stash` 来决定不与某些内容工作,也可以重写已经发生的提交就像它们以另一种方式发生的一样。 +这可能涉及改变提交的顺序,改变提交中的信息或修改文件,将提交压缩或是拆分,或完全地移除提交——在将你的工作成果与他人共享之前。 在本节中,你可以学到如何完成这些工作,这样在与他人分享你的工作成果时你的提交历史将如你所愿地展示出来。 @@ -22,8 +20,7 @@ Git 的基本原则之一是,由于克隆中有很多工作是本地的,因 ==== 修改最后一次提交 修改你最近一次提交可能是所有修改历史提交的操作中最常见的一个。 -对于你的最近一次提交,你往往想做两件事情:简单地修改提交信息, -或者通过添加、移除或修改文件来更改提交实际的内容。 +对于你的最近一次提交,你往往想做两件事情:简单地修改提交信息,或者通过添加、移除或修改文件来更改提交实际的内容。 如果,你只是想修改最近一次提交的提交信息,那么很简单: @@ -35,8 +32,7 @@ $ git commit --amend 上面这条命令会将最后一次的提交信息载入到编辑器中供你修改。 当保存并关闭编辑器后,编辑器会将更新后的提交信息写入新提交中,它会成为新的最后一次提交。 -另一方面,如果你想要修改最后一次提交的实际内容,那么流程很相似:首先作出你想要补上的修改, -暂存它们,然后用 `git commit --amend` 以新的改进后的提交来 *替换* 掉旧有的最后一次提交, +另一方面,如果你想要修改最后一次提交的实际内容,那么流程很相似:首先作出你想要补上的修改,暂存它们,然后用 `git commit --amend` 以新的改进后的提交来 *替换* 掉旧有的最后一次提交。 使用这个技巧的时候需要小心,因为修正会改变提交的 SHA-1 校验和。 它类似于一个小的变基——如果已经推送了最后一次提交就不要修正它。 @@ -47,8 +43,7 @@ $ git commit --amend 当你在修补一次提交时,可以同时修改提交信息和提交内容。 如果你修补了提交的内容,那么几乎肯定要更新提交消息以反映修改后的内容。 -另一方面,如果你的修补是琐碎的(如修改了一个笔误或添加了一个忘记暂存的文件), -那么之前的提交信息不必修改,你只需作出更改,暂存它们,然后通过以下命令避免不必要的编辑器环节即可: +另一方面,如果你的修补是琐碎的(如修改了一个笔误或添加了一个忘记暂存的文件),那么之前的提交信息不必修改,你只需作出更改,暂存它们,然后通过以下命令避免不必要的编辑器环节即可: [source,console] ---- @@ -66,8 +61,7 @@ Git 没有一个改变历史工具,但是可以使用变基工具来变基一 可以通过给 `git rebase` 增加 `-i` 选项来交互式地运行变基。 必须指定想要重写多久远的历史,这可以通过告诉命令将要变基到的提交来做到。 -例如,如果想要修改最近三次提交信息,或者那组提交中的任意一个提交信息, -将想要修改的最近一次提交的父提交作为参数传递给 `git rebase -i` 命令,即 `HEAD~2^` 或 `HEAD~3`。 +例如,如果想要修改最近三次提交信息,或者那组提交中的任意一个提交信息,将想要修改的最近一次提交的父提交作为参数传递给 `git rebase -i` 命令,即 `HEAD~2^` 或 `HEAD~3`。 记住 `~3` 可能比较容易,因为你正尝试修改最后三次提交;但是注意实际上指定了以前的四次提交,即想要修改提交的父提交: [source,console] @@ -156,7 +150,7 @@ Once you're satisfied with your changes, run ---- 这些指令准确地告诉你该做什么。 -输入 +输入: [source,console] ---- @@ -164,7 +158,7 @@ $ git commit --amend ---- 修改提交信息,然后退出编辑器。 -然后,运行 +然后,运行: [source,console] ---- @@ -276,8 +270,7 @@ pick a5f4a0d added cat-file ---- 然后,当脚本带你进入到命令行时,重置那个提交,拿到被重置的修改,从中创建几次提交。 -当保存并退出编辑器时,Git 带你到列表中第一个提交的父提交,应用第一个提交(`f7f3f6d`), -应用第二个提交(`310154e`),然后让你进入命令行。 +当保存并退出编辑器时,Git 带你到列表中第一个提交的父提交,应用第一个提交(`f7f3f6d`),应用第二个提交(`310154e`),然后让你进入命令行。 那里,可以通过 `git reset HEAD^` 做一次针对那个提交的混合重置,实际上将会撤消那次提交并将修改的文件取消暂存。 现在可以暂存并提交文件直到有几个提交,然后当完成时运行 `git rebase --continue`: @@ -302,7 +295,36 @@ $ git log -4 --pretty=format:"%h %s" f3cc40e changed my name a bit ---- -再次强调,这些改动了所有在列表中的提交的 SHA-1 校验和,所以要确保列表中的提交还没有推送到共享仓库中。 +这会改变列表中最近三次提交的 SHA-1 哈希值,因此请务必确保这列表中包含的修改过的提交,没有任何一个已经被推送到了共享仓库中。 +请注意,列表中最后的那个提交(`f7f3f6d`)并未发生改变。 +尽管该提交出现在脚本中,但因为它被标记为“pick”,并且是在发生任何变基修改之前被应用,所以 Git 会保持该提交不变。 + +==== 删除提交 + +如果你想移除某个提交,可以使用 `rebase -i` 脚本将其删除。 +在提交列表中,把你想要删除的提交所在行前面的单词改为 “drop”(或者直接将那一行从变基脚本中删掉): + +[source,console] +---- +pick 461cb2a This commit is OK +drop 5aecc10 This commit is broken +---- + +由于 Git 构建提交对象的方式,删除或修改一个提交会导致其后的所有提交被重新编写。 +你在仓库历史中退得越远,需要重新创建的提交就越多。 +如果你序列中后面的许多提交依赖于你刚刚删除的那一个,这可能会引发大量的合并冲突。 + +如果你在此类变基进行到一半时发现这并非明智之举,随时可以终止该操作。 +输入 `git rebase --abort`,你的仓库就会恢复到开始变基之前的状态。 + +如果你完成了变基,却发现结果并非你所期望的,可以使用 `git reflog` 来恢复分支的较早版本。 +有关 `reflog` 命令的更多信息,请参阅 <> 。 + +[NOTE] +==== +Drew DeVault 编写了一份包含动手实践练习的实用指南,旨在帮助大家学习如何使用 `git rebase`。 +你可以在这里找到它: https://git-rebase.io/[^] +==== ==== 核武器级选项:filter-branch @@ -314,8 +336,8 @@ f3cc40e changed my name a bit [CAUTION] ==== `git filter-branch` 有很多陷阱,不再推荐使用它来重写历史。 -请考虑使用 `git-filter-repo`,它是一个 Python 脚本,相比大多数使用 `filter-branch` -的应用来说,它做得要更好。它的文档和源码可访问 https://github.com/newren/git-filter-repo[] 获取。 +请考虑使用 `git-filter-repo`,它是一个 Python 脚本,相比大多数使用 `filter-branch` 的应用来说,它做得要更好。 +它的文档和源码可访问 https://github.com/newren/git-filter-repo[^] 获取。 ==== [[_removing_file_every_commit]] @@ -359,8 +381,7 @@ Git 会自动移除所有不影响子目录的提交。 ===== 全局修改邮箱地址 -另一个常见的情形是在你开始工作时忘记运行 `git config` 来设置你的名字与邮箱地址, -或者你想要开源一个项目并且修改所有你的工作邮箱地址为你的个人邮箱地址。 +另一个常见的情形是在你开始工作时忘记运行 `git config` 来设置你的名字与邮箱地址,或者你想要开源一个项目并且修改所有你的工作邮箱地址为你的个人邮箱地址。 任何情形下,你也可以通过 `filter-branch` 来一次性修改多个提交中的邮箱地址。 需要小心的是只修改你自己的邮箱地址,所以你使用 `--commit-filter`: @@ -378,5 +399,4 @@ $ git filter-branch --commit-filter ' ---- 这会遍历并重写每一个提交来包含你的新邮箱地址。 -因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA-1 校验和, -而不仅仅只是那些匹配邮箱地址的提交。 +因为提交包含了它们父提交的 SHA-1 校验和,这个命令会修改你的历史中的每一个提交的 SHA-1 校验和,而不仅仅只是那些匹配邮箱地址的提交。 diff --git a/book/07-git-tools/sections/searching.asc b/book/07-git-tools/sections/searching.asc index d917424c..d0f5053b 100644 --- a/book/07-git-tools/sections/searching.asc +++ b/book/07-git-tools/sections/searching.asc @@ -32,8 +32,7 @@ git-compat-util.h:1140:#define gmtime_r git_gmtime_r 除了上面的基本搜索命令外,`git grep` 还支持大量其它有趣的选项。 -例如,若不想打印所有匹配的项,你可以使用 `-c` 或 `--count` 选项来让 `git grep` 输出概述的信息, -其中仅包括那些包含匹配字符串的文件,以及每个文件中包含了多少个匹配。 +例如,若不想打印所有匹配的项,你可以使用 `-c` 或 `--count` 选项来让 `git grep` 输出概述的信息,其中仅包括那些包含匹配字符串的文件,以及每个文件中包含了多少个匹配。 [source,console] ---- @@ -45,8 +44,7 @@ date.c:3 git-compat-util.h:2 ---- -如果你还关心搜索字符串的 *上下文*,那么可以传入 `-p` 或 `--show-function` -选项来显示每一个匹配的字符串所在的方法或函数: +如果你还关心搜索字符串的 *上下文*,那么可以传入 `-p` 或 `--show-function` 选项来显示每一个匹配的字符串所在的方法或函数: [source,console] ---- @@ -59,12 +57,10 @@ date.c=int parse_date_basic(const char *date, timestamp_t *timestamp, int *offse date.c: /* gmtime_r() in match_digit() may have clobbered it */ ---- -如你所见,date.c 文件中的 `match_multi_number` 和 `match_digit` 两个函数都调用了 `gmtime_r` 例程 -(第三个显示的匹配只是注释中的字符串)。 +如你所见,date.c 文件中的 `match_multi_number` 和 `match_digit` 两个函数都调用了 `gmtime_r` 例程(第三个显示的匹配只是注释中的字符串)。 你还可以使用 `--and` 标志来查看复杂的字符串组合,它确保了多个匹配出现在同一文本行中。 -比如,我们要查看在旧版本 1.8.0 的 Git 代码库中定义了常量名包含 “LINK” 或者 “BUF_MAX” 这两个字符串的行 -(这里也用到了 `--break` 和 `--heading` 选项来使输出更加容易阅读)。 +比如,我们要查看在旧版本 1.8.0 的 Git 代码库中定义了常量名包含 “LINK” 或者 “BUF_MAX” 这两个字符串的行(这里也用到了 `--break` 和 `--heading` 选项来使输出更加容易阅读)。 [source,console] ---- @@ -100,8 +96,7 @@ v1.8.0:zlib.c 或许你不想知道某一项在 *哪里* ,而是想知道是什么 *时候* 存在或者引入的。 `git log` 命令有许多强大的工具可以通过提交信息甚至是 diff 的内容来找到某个特定的提交。 -例如,如果我们想找到 `ZLIB_BUF_MAX` 常量是什么时候引入的,我们可以使用 `-S` 选项 -(在 Git 中俗称“鹤嘴锄(pickaxe)”选项)来显示新增和删除该字符串的提交。 +例如,如果我们想找到 `ZLIB_BUF_MAX` 常量是什么时候引入的,我们可以使用 `-S` 选项(在 Git 中俗称“鹤嘴锄(pickaxe)”选项)来显示新增和删除该字符串的提交。 [source,console] ---- @@ -119,8 +114,7 @@ ef49a7a zlib: zlib can only process 4GB at a time 行日志搜索是另一个相当高级并且有用的日志搜索功能。 在 `git log` 后加上 `-L` 选项即可调用,它可以展示代码中一行或者一个函数的历史。 -例如,假设我们想查看 `zlib.c` 文件中 `git_deflate_bound` 函数的每一次变更, -我们可以执行 `git log -L :git_deflate_bound:zlib.c`。 +例如,假设我们想查看 `zlib.c` 文件中 `git_deflate_bound` 函数的每一次变更,我们可以执行 `git log -L :git_deflate_bound:zlib.c`。 Git 会尝试找出这个函数的范围,然后查找历史记录,并且显示从函数创建之后一系列变更对应的补丁。 [source,console] diff --git a/book/07-git-tools/sections/signing.asc b/book/07-git-tools/sections/signing.asc index b7926dde..53521bb6 100644 --- a/book/07-git-tools/sections/signing.asc +++ b/book/07-git-tools/sections/signing.asc @@ -2,8 +2,7 @@ === 签署工作 Git 虽然是密码级安全的,但它不是万无一失的。 -如果你从因特网上的其他人那里拿取工作,并且想要验证提交是不是真正地来自于可信来源, -Git 提供了几种通过 GPG 来签署和验证工作的方式。 +如果你从因特网上的其他人那里拿取工作,并且想要验证提交是不是真正地来自于可信来源,Git 提供了几种通过 GPG 来签署和验证工作的方式。 ==== GPG 介绍 @@ -158,8 +157,7 @@ a11bef0 N Scott Chacon first commit 这里我们可以看到只有最后一次提交是签署并有效的,而之前的提交都不是。 -在 Git 1.8.3 及以后的版本中,`git merge` 与 `git pull` 可以使用 -`--verify-signatures` 选项来检查并拒绝没有携带可信 GPG 签名的提交。 +在 Git 1.8.3 及以后的版本中,`git merge` 与 `git pull` 可以使用 `--verify-signatures` 选项来检查并拒绝没有携带可信 GPG 签名的提交。 如果使用这个选项来合并一个包含未签名或有效的提交的分支时,合并不会生效。 @@ -201,5 +199,6 @@ Merge made by the 'recursive' strategy. ==== 每个人必须签署 签署标签与提交很棒,但是如果决定在正常的工作流程中使用它,你必须确保团队中的每一个人都理解如何这样做。 +这可以通过要求每一个参与该仓库工作的人运行 `git config --local commit.gpgsign true` 来实现,从而在默认情况下让该仓库中的所有提交都自动进行签名。 如果没有,你将会花费大量时间帮助其他人找出并用签名的版本重写提交。 在采用签署成为标准工作流程的一部分前,确保你完全理解 GPG 及签署带来的好处。 diff --git a/book/07-git-tools/sections/stashing-cleaning.asc b/book/07-git-tools/sections/stashing-cleaning.asc index b9f9da64..884561f2 100644 --- a/book/07-git-tools/sections/stashing-cleaning.asc +++ b/book/07-git-tools/sections/stashing-cleaning.asc @@ -1,20 +1,17 @@ [[_git_stashing]] === 贮藏与清理 -有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, -而这时你想要切换到另一个分支做一点别的事情。 +有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是 `git stash` 命令。 -贮藏(stash)会处理工作目录的脏的状态——即跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, -而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。 +贮藏(stash)会处理工作目录的脏的状态——即跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。 [NOTE] .迁移到 `git stash push` ==== -截至 2017 年 10 月下旬,Git 邮件列表上进行了广泛讨论,该讨论中弃用了 `git stash save` 命令, -代之以现有 `git stash push` 命令。主因是 `git stash push` 引入了贮藏选定的 *路径规范* 的选项, -而有些东西 `git stash save` 不支持。 +截至 2017 年 10 月下旬,Git 邮件列表上进行了广泛讨论,该讨论中弃用了 `git stash save` 命令,代之以现有 `git stash push` 命令。 +主因是 `git stash push` 引入了贮藏选定的 *路径规范* 的选项,而有些东西 `git stash save` 不支持。 `git stash save` 不会很快就消失,所以不用担心它突然不见。 不过你可能想要迁移到 `push` 来获取新功能。 @@ -155,8 +152,7 @@ M index.html 另一个经常使用贮藏来做的事情是像贮藏跟踪文件一样贮藏未跟踪文件。 默认情况下,`git stash` 只会贮藏已修改和暂存的 *已跟踪* 文件。 如果指定 `--include-untracked` 或 `-u` 选项,Git 也会贮藏任何未跟踪文件。 -然而,在贮藏中包含未跟踪的文件仍然不会包含明确 *忽略* 的文件。 -要额外包含忽略的文件,请使用 `--all` 或 `-a` 选项。 +然而,在贮藏中包含未跟踪的文件仍然不会包含明确 *忽略* 的文件。要额外包含忽略的文件,请使用 `--all` 或 `-a` 选项。 [source,console] ---- @@ -173,8 +169,7 @@ $ git status -s $ ---- -最终,如果指定了 `--patch` 标记,Git 不会贮藏所有修改过的任何东西, -但是会交互式地提示哪些改动想要贮藏、哪些改动需要保存在工作目录中。 +最终,如果指定了 `--patch` 标记,Git 不会贮藏所有修改过的任何东西,但是会交互式地提示哪些改动想要贮藏、哪些改动需要保存在工作目录中。 [source,console] ---- @@ -199,13 +194,11 @@ Stash this hunk [y,n,q,a,d,/,e,?]? y Saved working directory and index state WIP on master: 1b65b17 added the index file ---- - ==== 从贮藏创建一个分支 如果贮藏了一些工作,将它留在那儿了一会儿,然后继续在贮藏的分支上工作,在重新应用工作时可能会有问题。 如果应用尝试修改刚刚修改的文件,你会得到一个合并冲突并不得不解决它。 -如果想要一个轻松的方式来再次测试贮藏的改动,可以运行 `git stash branch ` -以你指定的分支名创建一个新分支,检出贮藏工作时所在的提交,重新在那应用工作,然后在应用成功后丢弃贮藏: +如果想要一个轻松的方式来再次测试贮藏的改动,可以运行 `git stash branch ` 以你指定的分支名创建一个新分支,检出贮藏工作时所在的提交,重新在那应用工作,然后在应用成功后丢弃贮藏: [source,console] ---- @@ -233,11 +226,9 @@ Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014) [[_git_clean]] ==== 清理工作目录 -对于工作目录中一些工作或文件,你想做的也许不是贮藏而是移除。 -`git clean` 命令就是用来干这个的。 +对于工作目录中一些工作或文件,你想做的也许不是贮藏而是移除。`git clean` 命令就是用来干这个的。 -清理工作目录有一些常见的原因,比如说为了移除由合并或外部工具生成的东西, -或是为了运行一个干净的构建而移除之前构建的残留。 +清理工作目录有一些常见的原因,比如说为了移除由合并或外部工具生成的东西,或是为了运行一个干净的构建而移除之前构建的残留。 你需要谨慎地使用这个命令,因为它被设计为从工作目录中移除未被追踪的文件。 如果你改变主意了,你也不一定能找回来那些文件的内容。 @@ -247,8 +238,7 @@ Dropped refs/stash@{0} (29d385a81d163dfd45a452a2ce816487a6b8b014) 使用 `git clean -f -d` 命令来移除工作目录中所有未追踪的文件以及空的子目录。 `-f` 意味着“强制(force)”或“确定要移除”,使用它需要 Git 配置变量 `clean.requireForce` 没有显式设置为 `false`。 -如果只是想要看看它会做什么,可以使用 `--dry-run` 或 `-n` 选项来运行命令, -这意味着“做一次演习然后告诉你 *将要* 移除什么”。 +如果只是想要看看它会做什么,可以使用 `--dry-run` 或 `-n` 选项来运行命令,这意味着“做一次演习然后告诉你 *将要* 移除什么”。 [source,console] ---- @@ -259,8 +249,7 @@ Would remove tmp/ 默认情况下,`git clean` 命令只会移除没有忽略的未跟踪文件。 任何与 `.gitignore` 或其他忽略文件中的模式匹配的文件都不会被移除。 -如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 `.o` 文件, -可以给 clean 命令增加一个 `-x` 选项。 +如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 `.o` 文件,可以给 clean 命令增加一个 `-x` 选项。 [source,console] ---- @@ -300,6 +289,6 @@ What now> [NOTE] ==== 在一种奇怪的情况下,可能需要格外用力才能让 Git 清理你的工作目录。 -如果你恰好在工作目录中复制或克隆了其他 Git 仓库(可能是子模块),那么即便是 -`git clean -fd` 都会拒绝删除这些目录。这种情况下,你需要加上第二个 `-f` 选项来强调。 +如果你恰好在工作目录中复制或克隆了其他 Git 仓库(可能是子模块),那么即便是 `git clean -fd` 都会拒绝删除这些目录。 +这种情况下,你需要加上第二个 `-f` 选项来强调。 ==== diff --git a/book/07-git-tools/sections/submodules.asc b/book/07-git-tools/sections/submodules.asc index 9f97f878..2f895756 100644 --- a/book/07-git-tools/sections/submodules.asc +++ b/book/07-git-tools/sections/submodules.asc @@ -185,8 +185,7 @@ Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b2 现在 `DbConnector` 子目录是处在和之前提交时相同的状态了。 不过还有更简单一点的方式。 -如果给 `git clone` 命令传递 `--recurse-submodules` 选项,它就会自动初始化并更新仓库中的每一个子模块, -包括可能存在的嵌套子模块。 +如果给 `git clone` 命令传递 `--recurse-submodules` 选项,它就会自动初始化并更新仓库中的每一个子模块,包括可能存在的嵌套子模块。 [source,console] ---- @@ -207,9 +206,8 @@ Checking connectivity... done. Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc' ---- -如果你已经克隆了项目但忘记了 `--recurse-submodules`,那么可以运行 `git submodule update --init` -将 `git submodule init` 和 `git submodule update` 合并成一步。如果还要初始化、抓取并检出任何嵌套的子模块, -请使用简明的 `git submodule update --init --recursive`。 +如果你已经克隆了项目但忘记了 `--recurse-submodules`,那么可以运行 `git submodule update --init` 将 `git submodule init` 和 `git submodule update` 合并成一步。 +如果还要初始化、抓取并检出任何嵌套的子模块,请使用简明的 `git submodule update --init --recursive`。 ==== 在包含子模块的项目上工作 @@ -266,8 +264,7 @@ Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0 此命令默认会假定你想要更新并检出子模块仓库的 `master` 分支。 不过你也可以设置为想要的其他分支。 -例如,你想要 DbConnector 子模块跟踪仓库的 “stable” 分支,那么既可以在 `.gitmodules` 文件中设置 -(这样其他人也可以跟踪它),也可以只在本地的 `.git/config` 文件中设置。 +例如,你想要 DbConnector 子模块跟踪仓库的 “stable” 分支,那么既可以在 `.gitmodules` 文件中设置(这样其他人也可以跟踪它),也可以只在本地的 `.git/config` 文件中设置。 让我们在 `.gitmodules` 文件中设置它: [source,console] @@ -380,6 +377,7 @@ Submodule DbConnector c3f01dc..c87d55d: 所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。 ===== 从项目远端拉取上游更改 + 现在,让我们站在协作者的视角,他有自己的 `MainProject` 仓库的本地克隆, 只是执行 `git pull` 获取你新提交的更改还不够: @@ -418,7 +416,8 @@ no changes added to commit (use "git add" and/or "git commit -a") ---- 默认情况下,`git pull` 命令会递归地抓取子模块的更改,如上面第一个命令的输出所示。 -然而,它不会 *更新* 子模块。这点可通过 `git status` 命令看到,它会显示子模块“已修改”,且“有新的提交”。 +然而,它不会 *更新* 子模块。 +这点可通过 `git status` 命令看到,它会显示子模块“已修改”,且“有新的提交”。 此外,左边的尖括号(<)指出了新的提交,表示这些提交已在 MainProject 中记录,但尚未在本地的 `DbConnector` 中检出。 为了完成更新,你需要运行 `git submodule update`: @@ -433,19 +432,16 @@ Your branch is up-to-date with 'origin/master'. nothing to commit, working tree clean ---- -请注意,为安全起见,如果 MainProject 提交了你刚拉取的新子模块,那么应该在 -`git submodule update` 后面添加 `--init` 选项,如果子模块有嵌套的子模块,则应使用 `--recursive` 选项。 +请注意,为安全起见,如果 MainProject 提交了你刚拉取的新子模块,那么应该在 `git submodule update` 后面添加 `--init` 选项,如果子模块有嵌套的子模块,则应使用 `--recursive` 选项。 如果你想自动化此过程,那么可以为 `git pull` 命令添加 `--recurse-submodules` 选项(从 Git 2.14 开始)。 这会让 Git 在拉取后运行 `git submodule update`,将子模块置为正确的状态。 -此外,如果你想让 Git 总是以 `--recurse-submodules` 拉取,可以将配置选项 `submodule.recurse` 设置为 `true` -(从 Git 2.15 开始可用于 `git pull`)。此选项会让 Git 为所有支持 `--recurse-submodules` 的命令使用该选项(除 `clone` 以外)。 +此外,如果你想让 Git 总是以 `--recurse-submodules` 拉取,可以将配置选项 `submodule.recurse` 设置为 `true`(从 Git 2.15 开始可用于 `git pull`)。 +此选项会让 Git 为所有支持 `--recurse-submodules` 的命令使用该选项(除 `clone` 以外)。 -在为父级项目拉取更新时,还会出现一种特殊的情况:在你拉取的提交中, -可能 `.gitmodules` 文件中记录的子模块的 URL 发生了改变。 +在为父级项目拉取更新时,还会出现一种特殊的情况:在你拉取的提交中,可能 `.gitmodules` 文件中记录的子模块的 URL 发生了改变。 比如,若子模块项目改变了它的托管平台,就会发生这种情况。 -此时,若父级项目引用的子模块提交不在仓库中本地配置的子模块远端上,那么执行 -`git pull --recurse-submodules` 或 `git submodule update` 就会失败。 +此时,若父级项目引用的子模块提交不在仓库中本地配置的子模块远端上,那么执行 `git pull --recurse-submodules` 或 `git submodule update` 就会失败。 为了补救,需要借助 `git submodule sync` 命令: [source,console] @@ -463,11 +459,10 @@ $ git submodule update --init --recursive 现在我们将通过一个例子来演示如何在子模块与主项目中同时做修改,以及如何同时提交与发布那些修改。 -到目前为止,当我们运行 `git submodule update` 从子模块仓库中抓取修改时, -Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作“游离的 HEAD”的状态。 +到目前为止,当我们运行 `git submodule update` 从子模块仓库中抓取修改时,Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作“游离的 HEAD”的状态。 这意味着没有本地工作分支(例如 “master” )跟踪改动。 -如果没有工作分支跟踪更改,也就意味着即便你将更改提交到了子模块,这些更改也很可能会在下次运行 -`git submodule update` 时丢失。如果你想要在子模块中跟踪这些修改,还需要一些额外的步骤。 +如果没有工作分支跟踪更改,也就意味着即便你将更改提交到了子模块,这些更改也很可能会在下次运行 `git submodule update` 时丢失。 +如果你想要在子模块中跟踪这些修改,还需要一些额外的步骤。 为了将子模块设置得更容易进入并修改,你需要做两件事。 首先,进入每个子模块并检出其相应的工作分支。 @@ -586,8 +581,8 @@ Submodule DbConnector c87d55d..82d2ad3: > add new option for conn pooling ---- -如果我们在主项目中提交并推送但并不推送子模块上的改动,其他尝试检出我们修改的人会遇到麻烦, -因为他们无法得到依赖的子模块改动。那些改动只存在于我们本地的拷贝中。 +如果我们在主项目中提交并推送但并不推送子模块上的改动,其他尝试检出我们修改的人会遇到麻烦,因为他们无法得到依赖的子模块改动。 +那些改动只存在于我们本地的拷贝中。 为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。 `git push` 命令接受可以设置为 “check” 或 “on-demand” 的 `--recurse-submodules` 参数。 @@ -703,7 +698,8 @@ $ git rev-parse HEAD eb41d764bccf88be77aced643c13a7fa86714135 $ git branch try-merge c771610 -(DbConnector) $ git merge try-merge + +$ git merge try-merge Auto-merging src/main.c CONFLICT (content): Merge conflict in src/main.c Recorded preimage for 'src/main.c' @@ -771,7 +767,8 @@ Automatic merge failed; fix conflicts and then commit the result. ---- Git 建议的命令是更新索引,就像你运行了 `git add` 那样,这样会清除冲突然后提交。 -不过你可能不应该这样做。你可以轻松地进入子模块目录,查看差异是什么,快进到这次提交,恰当地测试,然后提交它。 +不过你可能不应该这样做。 +你可以轻松地进入子模块目录,查看差异是什么,快进到这次提交,恰当地测试,然后提交它。 [source,console] ---- @@ -865,8 +862,7 @@ index 1aaefb6..5297645 100644 ===== 有用的别名 你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 -我们在 <> 介绍了设置 Git 别名, -但是如果你计划在 Git 中大量使用子模块的话,这里有一些例子。 +我们在 <> 介绍了设置 Git 别名,但是如果你计划在 Git 中大量使用子模块的话,这里有一些例子。 [source,console] ---- @@ -882,6 +878,7 @@ $ git config alias.supdate 'submodule update --remote --merge' 然而使用子模块还是有一些小问题。 ===== 切换分支 + 例如,使用 Git 2.13 以前的版本时,在有子模块的项目中切换分支可能会造成麻烦。 如果你创建一个新分支,在其中添加一个子模块,之后切换到没有该子模块的分支上时,你仍然会有一个还未跟踪的子模块目录。 @@ -941,8 +938,7 @@ Makefile includes scripts src 再说一遍,这真的不难,只是会让人有点儿困惑。 -新版的 Git(>= 2.13)通过为 `git checkout` 命令添加 `--recurse-submodules` 选项简化了所有这些步骤, -它能为了我们要切换到的分支让子模块处于的正确状态。 +新版的 Git(>= 2.13)通过为 `git checkout` 命令添加 `--recurse-submodules` 选项简化了所有这些步骤,它能为了我们要切换到的分支让子模块处于的正确状态。 [source,console] ---- @@ -972,19 +968,15 @@ Your branch is up-to-date with 'origin/master'. nothing to commit, working tree clean ---- -当你在父级项目的几个分支上工作时,对 `git checkout` 使用 `--recurse-submodules` 选项也很有用, -它能让你的子模块处于不同的提交上。确实,如果你在记录了子模块的不同提交的分支上切换, -那么在执行 `git status` 后子模块会显示为“已修改”并指出“新的提交”。 +当你在父级项目的几个分支上工作时,对 `git checkout` 使用 `--recurse-submodules` 选项也很有用,它能让你的子模块处于不同的提交上。 +确实,如果你在记录了子模块的不同提交的分支上切换,那么在执行 `git status` 后子模块会显示为“已修改”并指出“新的提交”。 这是因为子模块的状态默认不会在切换分支时保留。 这点非常让人困惑,因此当你的项目中拥有子模块时,可以总是使用 `git checkout --recurse-submodules`。 -(对于没有 `--recurse-submodules` 选项的旧版 Git,在检出之后可使用 -`git submodule update --init --recursive` 来让子模块处于正确的状态)。 +(对于没有 `--recurse-submodules` 选项的旧版 Git,在检出之后可使用 `git submodule update --init --recursive` 来让子模块处于正确的状态)。 -幸运的是,你可以通过 `git config submodule.recurse true` 设置 `submodule.recurse` 选项, -告诉 Git(>=2.14)总是使用 `--recurse-submodules`。 -如上所述,这也会让 Git 为每个拥有 `--recurse-submodules` 选项的命令(除了 `git clone`) -总是递归地在子模块中执行。 +幸运的是,你可以通过 `git config submodule.recurse true` 设置 `submodule.recurse` 选项,告诉 Git(>=2.14)总是使用 `--recurse-submodules`。 +如上所述,这也会让 Git 为每个拥有 `--recurse-submodules` 选项的命令(除了 `git clone`)总是递归地在子模块中执行。 ===== 从子目录切换到子模块 diff --git a/book/07-git-tools/sections/subtree-merges.asc b/book/07-git-tools/sections/subtree-merges.asc index 5e5f2166..a5f8a718 100644 --- a/book/07-git-tools/sections/subtree-merges.asc +++ b/book/07-git-tools/sections/subtree-merges.asc @@ -68,8 +68,7 @@ $ git pull ---- 接着,我们可以将这些变更合并回我们的 `master` 分支。 -使用 `--squash` 选项和使用 `-Xsubtree` 选项(它采用递归合并策略), -都可以用来可以拉取变更并且预填充提交信息。 +使用 `--squash` 选项和使用 `-Xsubtree` 选项(它采用递归合并策略),都可以用来可以拉取变更并且预填充提交信息。 (递归策略在这里是默认的,提到它是为了让读者有个清晰的概念。) [source,console] @@ -81,17 +80,14 @@ Automatic merge went well; stopped before committing as requested ---- Rack 项目中所有的改动都被合并了,等待被提交到本地。 -你也可以用相反的方法——在 `master` 分支上的 `rack` 子目录中做改动然后将它们合并入你的 -`rack_branch` 分支中,之后你可能将其提交给项目维护着或者将它们推送到上游。 +你也可以用相反的方法——在 `master` 分支上的 `rack` 子目录中做改动然后将它们合并入你的 `rack_branch` 分支中,之后你可能将其提交给项目维护着或者将它们推送到上游。 -这给我们提供了一种类似子模块工作流的工作方式,但是它并不需要用到子模块 -(有关子模块的内容我们会在 <> 中介绍)。 +这给我们提供了一种类似子模块工作流的工作方式,但是它并不需要用到子模块(有关子模块的内容我们会在 <> 中介绍)。 我们可以在自己的仓库中保持一些和其他项目相关的分支,偶尔使用子树合并将它们合并到我们的项目中。 某些时候这种方式很有用,例如当所有的代码都提交到一个地方的时候。 然而,它同时也有缺点,它更加复杂且更容易让人犯错,例如重复合并改动或者不小心将分支提交到一个无关的仓库上去。 -另外一个有点奇怪的地方是,当你想查看 `rack` 子目录和 `rack_branch` 分支的差异—— -来确定你是否需要合并它们——你不能使用普通的 `diff` 命令。 +另外一个有点奇怪的地方是,当你想查看 `rack` 子目录和 `rack_branch` 分支的差异——来确定你是否需要合并它们——你不能使用普通的 `diff` 命令。 取而代之的是,你必须使用 `git diff-tree` 来和你的目标分支做比较: [source,console] diff --git a/book/08-customizing-git/sections/attributes.asc b/book/08-customizing-git/sections/attributes.asc index 57f92203..63f7ad9c 100644 --- a/book/08-customizing-git/sections/attributes.asc +++ b/book/08-customizing-git/sections/attributes.asc @@ -17,11 +17,10 @@ ===== 识别二进制文件 有些文件表面上是文本文件,实质上应被作为二进制文件处理。 -例如,macOS 平台上的 Xcode 项目会包含一个以 `.pbxproj` 结尾的文件, -它通常是一个记录项目构建配置等信息的 JSON(纯文本 Javascript 数据类型)数据集,由 IDE 写入磁盘。 -虽然技术上看它是由 UTF-8 编码的文本文件,但你并不会希望将它当作文本文件来处理, -因为它其实是一个轻量级数据库——如果有两个人修改了它,你通常无法合并内容,diff 的输出也帮不上什么忙。 -它本应被机器处理。因此,你想把它当成二进制文件。 +例如,macOS 平台上的 Xcode 项目会包含一个以 `.pbxproj` 结尾的文件,它通常是一个记录项目构建配置等信息的 JSON(纯文本 Javascript 数据类型)数据集,由 IDE 写入磁盘。 +虽然技术上看它是由 UTF-8 编码的文本文件,但你并不会希望将它当作文本文件来处理,因为它其实是一个轻量级数据库——如果有两个人修改了它,你通常无法合并内容,diff 的输出也帮不上什么忙。 +它本应被机器处理。 +因此,你想把它当成二进制文件。 要让 Git 把所有 `pbxproj` 文件当成二进制文件,在 `.gitattributes` 文件中如下设置: @@ -38,7 +37,6 @@ 秘诀在于,告诉 Git 怎么把你的二进制文件转化为文本格式,从而能够使用普通的 diff 方式进行对比。 首先,让我们尝试用这个技术解决世人最头疼的问题之一:对 Microsoft Word 文档进行版本控制。 -大家都知道,Microsoft Word 几乎是世上最难缠的编辑器,尽管如此,大家还是在用它。 如果想对 Word 文档进行版本控制,你可以把文件加入到 Git 库中,每次修改后提交即可。但这样做有什么实际意义呢? 毕竟运行 `git diff` 命令后,你只能得到如下的结果: @@ -64,7 +62,7 @@ Git 属性能很好地解决此问题。 我们现在就来设置它。 我们会对 Git 进行配置,令其能够借助 `docx2txt` 程序将 Word 文档转为可读文本文件,这样不同的文件间就能够正确比较了。 -首先,你需要安装 `docx2txt`;它可以从 https://sourceforge.net/projects/docx2txt[] 下载。 +首先,你需要安装 `docx2txt`;它可以从 https://sourceforge.net/projects/docx2txt[^] 下载。 按照 `INSTALL` 文件的说明,把它放到你的可执行路径下。 接下来,你还需要写一个脚本把输出结果包装成 Git 支持的格式。 在你的可执行路径下创建一个叫 `docx2txt` 文件,添加这些内容: @@ -112,7 +110,7 @@ Git 成功地挑出了我们添加的那句话“Testing: 1, 2, 3.”,一字 你还能用这个方法比较图像文件。 其中一个办法是,在比较时对图像文件运用一个过滤器,提炼出 EXIF 信息——这是在大部分图像格式中都有记录的一种元数据。 -如果你下载并安装了 `exiftool` 程序,可以利用它将图像转换为关于元数据的文本信息,这样比较时至少能以文本的形式显示发生过的变动: +如果你下载并安装了 `exiftool` 程序,可以利用它将图像转换为关于元数据的文本信息,这样比较时至少能以文本的形式显示发生过的变动。 将以下内容放到你的 `.gitattributes` 文件中: [source,ini] @@ -190,8 +188,7 @@ $Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $ ---- 然而,这个结果的用途比较有限。 -如果用过 CVS 或 Subversion 的关键字替换功能,我们会想加上一个时间戳信息——光有 SHA-1 校验和用途不大, -它仅仅是个随机字符串,你无法凭字面值来区分不同 SHA-1 时间上的先后。 +如果用过 CVS 或 Subversion 的关键字替换功能,我们会想加上一个时间戳信息——光有 SHA-1 校验和用途不大,它仅仅是个随机字符串,你无法凭字面值来区分不同 SHA-1 时间上的先后。 因此 Git 属性提供了另一种方法:我们可以编写自己的过滤器来实现文件提交或检出时的关键字替换。 一个过滤器由“clean”和“smudge”两个子过滤器组成。 @@ -250,8 +247,7 @@ $ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/" ---- 这段 Perl 代码会删除 `$Date$` 后面注入的内容,恢复它的原貌。 -过滤器终于准备完成了,是时候测试一下。创建一个带有 `$Date$` 关键字的文件, -然后给它设置一个 Git 属性,关联我们的新过滤器: +过滤器终于准备完成了,是时候测试一下。创建一个带有 `$Date$` 关键字的文件,然后给它设置一个 Git 属性,关联我们的新过滤器: [source,ini] ---- @@ -301,12 +297,9 @@ test/ export-ignore ===== `export-subst` -在导出文件进行部署的时候,你可以将 `git log` 的格式化和关键字展开处理应用到标记了 -`export-subst` 属性的部分文件。 +在导出文件进行部署的时候,你可以将 `git log` 的格式化和关键字展开处理应用到标记了 `export-subst` 属性的部分文件。 -举个例子,如果你想在项目中包含一个叫做 `LAST_COMMIT` 的文件, -并在运行 `git archive` 的时候自动向它注入最新提交的元数据,可以像这样设置 -`.gitattributes` 和 `LAST_COMMIT` 该文件: +举个例子,如果你想在项目中包含一个叫做 `LAST_COMMIT` 的文件,并在运行 `git archive` 的时候自动向它注入最新提交的元数据,可以像这样设置 `.gitattributes` 和 `LAST_COMMIT` 该文件: [source,ini] ---- diff --git a/book/08-customizing-git/sections/config.asc b/book/08-customizing-git/sections/config.asc index 92b35293..17133d28 100644 --- a/book/08-customizing-git/sections/config.asc +++ b/book/08-customizing-git/sections/config.asc @@ -22,6 +22,7 @@ $ git config --global user.email johndoe@example.com 最后 Git 会查找你正在操作的仓库所对应的 Git 目录下的配置文件(`.git/config`)。 这个文件中的值只对该仓库有效,它对应于向 `git config` 传递 `--local` 选项。 +如果你没有指定想要操作的具体层级,这就是默认选项。 以上三个层次中每层的配置(系统、全局、本地)都会覆盖掉上一层次的配置,所以 `.git/config` 中的值会覆盖掉 `/etc/gitconfig` 中所对应的值。 @@ -35,8 +36,7 @@ Git 的配置文件是纯文本的,所以你可以直接手动编辑这些配 Git 能够识别的配置项分为两大类:客户端和服务器端。 其中大部分属于客户端配置 —— 可以依你个人的工作偏好进行配置。 -尽管 Git 支持的选项 _繁多_,但其中大部分仅仅在某些罕见的情况下有意义。 -我们只讲述最平常和最有用的选项。 +尽管 Git 支持的选项 _繁多_,但其中大部分仅仅在某些罕见的情况下有意义。我们只讲述最平常和最有用的选项。 如果想得到你当前版本的 Git 支持的选项列表,请运行 [source,console] @@ -45,13 +45,17 @@ $ man git-config ---- 这个命令列出了所有可用的选项,以及与之相关的介绍。 -你也可以在 http://git-scm.com/docs/git-config[] 找到同样的内容。 +你也可以在 https://git-scm.com/docs/git-config/zh_HANS-CN[^] 找到同样的内容。 + +[NOTE] +==== +对于高级用例,你可能需要参阅上述文档中的“有条件的包括”(Conditional includes)章节。 +==== ===== `core.editor` ((($EDITOR)))((($VISUAL, see $EDITOR))) -默认情况下,Git 会调用你通过环境变量 `$VISUAL` 或 `$EDITOR` 设置的文本编辑器, -如果没有设置,默认则会调用 `vi` 来创建和编辑你的提交以及标签信息。 +默认情况下,Git 会调用你通过环境变量 `$VISUAL` 或 `$EDITOR` 设置的文本编辑器,如果没有设置,默认则会调用 `vi` 来创建和编辑你的提交以及标签信息。 你可以使用 `core.editor` 选项来修改默认的编辑器: [source,console] @@ -64,8 +68,7 @@ $ git config --global core.editor emacs ===== `commit.template` (((commit templates))) -如果把此项指定为你的系统上某个文件的路径,当你提交的时候, -Git 会使用该文件的内容作为提交的默认初始化信息。 +如果把此项指定为你的系统上某个文件的路径,当你提交的时候,Git 会使用该文件的内容作为提交的默认初始化信息。 创建的自定义提交模版中的值可以用来提示自己或他人适当的提交格式和风格。 例如:考虑以下 `~/.gitmessage.txt` 模板文件: @@ -80,8 +83,7 @@ feel free to be detailed. [Ticket: X] ---- -注意此提交模版是如何提示提交者保持主题的简短(为了精简 `git log --oneline` 的输出), -如何在后面添加进一步的详情,如何引用问题和 bug 跟踪系统的工单号(Ticket),如果有的话。 +注意此提交模版是如何提示提交者保持主题的简短(为了精简 `git log --oneline` 的输出),如何在后面添加进一步的详情,如何引用问题和 bug 跟踪系统的工单号(Ticket),如果有的话。 要想让 Git 把它作为运行 `git commit` 时显示在你的编辑器中的默认信息,如下设置 `commit.template`: @@ -132,8 +134,8 @@ $ git config --global core.pager '' ===== `user.signingkey` (((GPG))) -如果你要创建经签署的含附注的标签(正如 <> 所述), -那么把你的 GPG 签署密钥设置为配置项会更好。如下设置你的密钥 ID: +如果你要创建经签署的含附注的标签(正如 <> 所述),那么把你的 GPG 签署密钥设置为配置项会更好。 +如下设置你的密钥 ID: [source,console] ---- @@ -150,9 +152,7 @@ $ git tag -s ===== `core.excludesfile` (((excludes)))(((.gitignore))) -正如 <> 所述,你可以在你的项目的 `.gitignore` -文件里面规定无需纳入 Git 管理的文件的模板,这样它们既不会出现在未跟踪列表, -也不会在你运行 `git add` 后被暂存。 +正如 <> 所述,你可以在你的项目的 `.gitignore` 文件里面规定无需纳入 Git 管理的文件的模板,这样它们既不会出现在未跟踪列表,也不会在你运行 `git add` 后被暂存。 不过有些时候,你想要在你所有的版本库中忽略掉某一类文件。 如果你的操作系统是 macOS,很可能就是指 `.DS_Store`。 @@ -195,7 +195,8 @@ $ git chekcout master 在 0.1 秒钟后自动运行... ---- -注意提示信息中的“0.1 秒”。`help.autocorrect` 接受一个代表十分之一秒的整数。 +注意提示信息中的“0.1 秒”。 +`help.autocorrect` 接受一个代表十分之一秒的整数。 所以如果你把它设置为 50, Git 将在自动执行命令前给你 5 秒的时间改变主意。 ==== Git 中的着色 @@ -250,10 +251,9 @@ $ git config --global color.diff.meta "blue black bold" 这里我们以一个不错且免费的工具 —— Perforce 图形化合并工具(P4Merge) —— 来展示如何用一个外部的工具来合并和解决冲突。 P4Merge 可以在所有主流平台上运行,所以安装上应该没有什么困难。 -在这个例子中,我们使用的路径名可以直接应用在 macOS 和 Linux 上; -在 Windows 上,`/usr/local/bin` 需要被改为你的环境中可执行文件所在的目录路径。 +在这个例子中,我们使用的路径名可以直接应用在 macOS 和 Linux 上;在 Windows 上,`/usr/local/bin` 需要被改为你的环境中可执行文件所在的目录路径。 -首先, https://www.perforce.com/product/components/perforce-visual-merge-and-diff-tools[从Perforce 下载 P4Merge]。 +首先, https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge[从Perforce 下载 P4Merge^]。 接下来,你要编写一个全局包装脚本来运行你的命令。 我们会使用 Mac 上的路径来指定该脚本的位置,在其他系统上,它将是 `p4merge` 二进制文件所在的目录。 创建一个名为 `extMerge` 的脚本包装 merge 命令,让它把参数转发给 p4merge 二进制文件: @@ -302,6 +302,7 @@ $ git config --global mergetool.extMerge.cmd \ $ git config --global mergetool.extMerge.trustExitCode false $ git config --global diff.external extDiff ---- + 或编辑你的 `~/.gitconfig` 文件,添加以下各行: [source,ini] @@ -324,8 +325,8 @@ $ git diff 32d1776b1^ 32d1776b1 Git 将启动 P4Merge,而不是在命令行输出比较的结果,就像这样: -.P4Merge. -image::images/p4merge.png[P4Merge.] +.P4Merge +image::images/p4merge.png[P4Merge] 如果你尝试合并两个分支,随后遇到了合并冲突,运行 `git mergetool`,Git 会调用 P4Merge 让你通过图形界面来解决冲突。 @@ -395,8 +396,7 @@ $ git config --global merge.tool kdiff3 (((crlf)))(((line endings))) 假如你正在 Windows 上写程序,而你的同伴用的是其他系统(或相反),你可能会遇到 CRLF 问题。 这是因为 Windows 使用回车(CR)和换行(LF)两个字符来结束一行,而 macOS 和 Linux 只使用换行(LF)一个字符。 -虽然这是小问题,但它会极大地扰乱跨平台协作。许多 Windows 上的编辑器会悄悄把行尾的换行字符转换成回车和换行, -或在用户按下 Enter 键时,插入回车和换行两个字符。 +虽然这是小问题,但它会极大地扰乱跨平台协作。许多 Windows 上的编辑器会悄悄把行尾的换行字符转换成回车和换行,或在用户按下 Enter 键时,插入回车和换行两个字符。 Git 可以在你提交时自动地把回车和换行转换成换行,而在检出代码时把换行转换成回车和换行。 你可以用 `core.autocrlf` 来打开此项功能。 @@ -407,8 +407,7 @@ Git 可以在你提交时自动地把回车和换行转换成换行,而在检 $ git config --global core.autocrlf true ---- -如果使用以换行作为行结束符的 Linux 或 macOS,你不需要 Git 在检出文件时进行自动的转换; -然而当一个以回车加换行作为行结束符的文件不小心被引入时,你肯定想让 Git 修正。 +如果使用以换行作为行结束符的 Linux 或 macOS,你不需要 Git 在检出文件时进行自动的转换;然而当一个以回车加换行作为行结束符的文件不小心被引入时,你肯定想让 Git 修正。 你可以把 `core.autocrlf` 设置成 input 来告诉 Git 在提交时把回车和换行转换成换行,检出时不转换: [source,console] @@ -430,15 +429,13 @@ $ git config --global core.autocrlf false Git 预先设置了一些选项来探测和修正多余空白字符问题。 它提供了六种处理多余空白字符的主要选项 —— 其中三个默认开启,另外三个默认关闭,不过你可以自由地设置它们。 -默认被打开的三个选项是:`blank-at-eol`,查找行尾的空格;`blank-at-eof`,盯住文件底部的空行; -`space-before-tab`,警惕行头 tab 前面的空格。 +默认被打开的三个选项是:`blank-at-eol`,查找行尾的空格;`blank-at-eof`,盯住文件底部的空行;`space-before-tab`,警惕行头 tab 前面的空格。 默认被关闭的三个选项是:`indent-with-non-tab`,揪出以空格而非 tab 开头的行(你可以用 `tabwidth` 选项控制它);`tab-in-indent`,监视在行头表示缩进的 tab;`cr-at-eol`,告诉 Git 忽略行尾的回车。 通过设置 `core.whitespace`,你可以让 Git 按照你的意图来打开或关闭以逗号分割的选项。 要想关闭某个选项,你可以在输入设置选项时不指定它或在它前面加个 `-`。 -例如,如果你想要打开除`space-before-tab` 之外的所有选项,那么可以这样 -( `trailing-space` 涵盖了 `blank-at-eol` 和 `blank-at-eof` ): +例如,如果你想要打开除`space-before-tab` 之外的所有选项,那么可以这样( `trailing-space` 涵盖了 `blank-at-eol` 和 `blank-at-eof` ): [source,console] ---- @@ -517,5 +514,4 @@ $ git config --system receive.denyDeletes true 这样会禁止通过推送删除分支和标签 — 没有用户可以这么做。 要删除远程分支,必须从服务器手动删除引用文件。 -通过用户访问控制列表(ACL)也能够在用户级的粒度上实现同样的功能, -你将在 <> 一节学到具体的做法。 +通过用户访问控制列表(ACL)也能够在用户级的粒度上实现同样的功能,你将在 <> 一节学到具体的做法。 diff --git a/book/08-customizing-git/sections/hooks.asc b/book/08-customizing-git/sections/hooks.asc index 243bc121..057186fa 100644 --- a/book/08-customizing-git/sections/hooks.asc +++ b/book/08-customizing-git/sections/hooks.asc @@ -11,14 +11,13 @@ 钩子都被存储在 Git 目录下的 `hooks` 子目录中。 也即绝大部分项目中的 `.git/hooks` 。 -当你用 `git init` 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。 -这些脚本除了本身可以被调用外,它们还透露了被触发时所传入的参数。 -所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 -—— 你可以用 Ruby 或 Python,或任何你熟悉的语言编写它们。 +当你用 `git init` 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。这些脚本除了本身可以被调用外,它们还透露了被触发时所传入的参数。 +所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 —— 你可以用 Ruby 或 Python,或任何你熟悉的语言编写它们。 这些示例的名字都是以 `.sample` 结尾,如果你想启用它们,得先移除这个后缀。 把一个正确命名(不带扩展名)且可执行的文件放入 `.git` 目录下的 `hooks` 子目录中,即可激活该钩子脚本。 -这样一来,它就能被 Git 调用。接下来,我们会讲解常用的钩子脚本类型。 +这样一来,它就能被 Git 调用。 +接下来,我们会讲解常用的钩子脚本类型。 ==== 客户端钩子 @@ -28,8 +27,7 @@ [NOTE] ==== 需要注意的是,克隆某个版本库时,它的客户端钩子 *并不* 随同复制。 -如果需要靠这些脚本来强制维持某种策略,建议你在服务器端实现这一功能。 -(请参照 <> 中的例子。) +如果需要靠这些脚本来强制维持某种策略,建议你在服务器端实现这一功能。(请参照 <> 中的例子。) ==== ===== 提交工作流钩子 @@ -124,8 +122,10 @@ Git 的一些日常操作在运行时,偶尔会调用 `git gc --auto` 进行 `post-receive` 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。 它接受与 `pre-receive` 相同的标准输入数据。 -它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器, -或者更新问题追踪系统(ticket-tracking system) —— -甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。 -该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态, -所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。 +它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器,或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。 +该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态,所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。 + +[TIP] +==== +如果你正在编写一个别人需要阅读的脚本(script)或钩子(hook),请优先选择长格式的命令行参数标记;六个月后你会感谢我们这样做的。 +==== diff --git a/book/08-customizing-git/sections/policy.asc b/book/08-customizing-git/sections/policy.asc index 5817495b..6e1cdea0 100644 --- a/book/08-customizing-git/sections/policy.asc +++ b/book/08-customizing-git/sections/policy.asc @@ -44,8 +44,7 @@ puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})" 作为目标,假定每一条信息必须包含一条形似“ref: 1234”的字符串,因为你想把每一次提交对应到问题追踪系统(ticketing system)中的某个事项。 你要逐一检查每一条推送上来的提交内容,看看提交信息是否包含这么一个字符串,然后,如果某个提交里不包含这个字符串,以非零返回值退出从而拒绝此次推送。 -把 `$newrev` 和 `$oldrev` 变量的值传给一个叫做 `git rev-list` 的 Git 底层命令, -你可以获取所有提交的 SHA-1 值列表。 +把 `$newrev` 和 `$oldrev` 变量的值传给一个叫做 `git rev-list` 的 Git 底层命令,你可以获取所有提交的 SHA-1 值列表。 `git rev-list` 基本类似 `git log` 命令,但它默认只输出 SHA-1 值而已,没有其他信息。 所以要获取由一次提交到另一次提交之间的所有 SHA-1 值,可以像这样运行: @@ -59,13 +58,11 @@ dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a 17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475 ---- -你可以截取这些输出内容,循环遍历其中每一个 SHA-1 值,找出与之对应的提交信息, -然后用正则表达式来测试该信息包含的内容。 +你可以截取这些输出内容,循环遍历其中每一个 SHA-1 值,找出与之对应的提交信息,然后用正则表达式来测试该信息包含的内容。 下一步要实现从每个提交中提取出提交信息。 使用另一个叫做 `git cat-file` 的底层命令来获得原始的提交数据。 -我们将在 <> 了解到这些底层命令的细节; -现在暂时先看一下这条命令的输出: +我们将在 <> 了解到这些底层命令的细节;现在暂时先看一下这条命令的输出: [source,console] ---- @@ -170,8 +167,7 @@ end 既然拿到了用户权限的数据,接下来你需要找出提交都修改了哪些路径,从而才能保证推送者对所有这些路径都有权限。 -使用 `git log` 的 `--name-only` 选项(在 <> -一章中里简单地提过),我们可以轻而易举的找出一次提交里修改的文件: +使用 `git log` 的 `--name-only` 选项(在 <> 一章中里简单地提过),我们可以轻而易举的找出一次提交里修改的文件: [source,console] ---- @@ -274,7 +270,7 @@ error: failed to push some refs to 'git@gitserver:project.git' 你会看到每个被你的钩子拒之门外的引用都收到了一个 remote rejected 信息,它告诉你正是钩子无法成功运行导致了推送的拒绝。 又或者某人想修改一个自己不具备权限的文件然后推送了一个包含它的提交,他将看到类似的提示。 -比如,一个文档作者尝试推送一个修改到 `lib` 目录的提交,他会看到 +比如,一个文档作者尝试推送一个修改到 `lib` 目录的提交,他会看到: [source,console] ---- @@ -364,7 +360,7 @@ check_directory_perms 这和服务器端的脚本几乎一样,除了两个重要区别。 第一,ACL 文件的位置不同,因为这个脚本在当前工作目录运行,而非 `.git` 目录。 -ACL 文件的路径必须从 +ACL 文件的路径必须从: [source,ruby] ---- @@ -380,14 +376,14 @@ access = get_acl_access_data('.git/acl') 另一个重要区别是获取被修改文件列表的方式。 在服务器端的时候使用了查看提交纪录的方式,可是目前的提交都还没被记录下来呢,所以这个列表只能从暂存区域获取。 -和原来的 +也就是说,你不能写成: [source,ruby] ---- files_modified = `git log -1 --name-only --pretty=format:'' #{ref}` ---- -不同,现在要用 +而必须写成: [source,ruby] ---- @@ -431,6 +427,7 @@ target_shas.each do |sha| end end ---- + 此脚本使用了 <> 一章中不曾提到的语法。 通过运行这个命令可以获得一系列之前推送过的提交: @@ -440,8 +437,7 @@ end ---- `SHA^@` 语法会被解析成该提交的所有父提交。 -该命令会列出在远程分支最新的提交中可达的,却在所有我们尝试推送的提交的 -SHA-1 值的所有父提交中不可达的提交——也就是快进的提交。 +该命令会列出在远程分支最新的提交中可达的,却在所有我们尝试推送的提交的 SHA-1 值的所有父提交中不可达的提交——也就是快进的提交。 这个解决方案主要的问题在于它有可能很慢而且常常没有必要——只要你不用 `-f` 来强制推送,服务器就会自动给出警告并且拒绝接受推送。 然而,这是个不错的练习,而且理论上能帮助你避免一次以后可能不得不回头修补的变基。 diff --git a/book/09-git-and-other-scms/sections/client-hg.asc b/book/09-git-and-other-scms/sections/client-hg.asc index 857f83b3..4ce45158 100644 --- a/book/09-git-and-other-scms/sections/client-hg.asc +++ b/book/09-git-and-other-scms/sections/client-hg.asc @@ -8,7 +8,7 @@ DVCS 的宇宙里不只有 Git。 好消息是,如果你更喜欢 Git 的客户端行为但是工作在源代码由 Mercurial 控制的项目中,有一种使用 Git 作为 Mercurial 托管仓库的客户端的方法。 由于 Git 与服务器仓库是使用远程交互的,那么由远程助手实现的桥接方法就不会让人很惊讶。 -这个项目的名字是 git-remote-hg,可以在 https://github.com/felipec/git-remote-hg[] 找到。 +这个项目的名字是 git-remote-hg,可以在 https://github.com/felipec/git-remote-hg[^] 找到。 ===== git-remote-hg @@ -31,10 +31,10 @@ Git-remote-hg 有一个其他的依赖:`mercurial` Python 库。 $ pip install mercurial ---- -(如果未安装 Python,访问 https://www.python.org/[] 来获取它。) +(如果未安装 Python,访问 https://www.python.org/[^] 来获取它。) 需要做的最后一件事是安装 Mercurial 客户端。 -如果还没有安装的话请访问 https://www.mercurial-scm.org/ 来安装。 +如果还没有安装的话请访问 https://www.mercurial-scm.org/[^] 来安装。 现在已经准备好摇滚了。 你所需要的一切就是一个你可以推送的 Mercurial 仓库。 @@ -390,7 +390,6 @@ o 0 0a04b987be5a 2005-08-26 01:20 -0700 mpm 变更集 _8_、_9_ 与 _10_ 已经被创建出来并且属于 `permanent` 分支,但是旧的变更集依然在那里。 这会让使用 Mercurial 的团队成员非常困惑,所以要避免这种行为。 - ===== Mercurial 总结 Git 与 Mercurial 如此相似,以至于跨这两个系统进行工作十分流畅。 diff --git a/book/09-git-and-other-scms/sections/client-p4.asc b/book/09-git-and-other-scms/sections/client-p4.asc index e3fbba8c..938a3dc2 100644 --- a/book/09-git-and-other-scms/sections/client-p4.asc +++ b/book/09-git-and-other-scms/sections/client-p4.asc @@ -15,12 +15,12 @@ ===== Git Fusion (((Perforce, Git Fusion))) -Perforce 提供了一个叫作 Git Fusion 的产品(可在 http://www.perforce.com/git-fusion[] 获得),它将会在服务器这边同步 Perforce 服务器与 Git 仓库。 +Perforce 提供了一个叫作 Git Fusion 的产品(可在 http://www.perforce.com/git-fusion[^] 获得),它将会在服务器这边同步 Perforce 服务器与 Git 仓库。 ====== 设置 针对我们的例子,我们将会使用最简单的方式安装 Git Fusion:下载一个虚拟机来运行 Perforce 守护进程与 Git Fusion。 -可以从 http://www.perforce.com/downloads/Perforce/20-User[] 获得虚拟机镜像,下载完成后将它导入到你最爱的虚拟机软件中(我们将会使用 VirtualBox)。 +可以从 http://www.perforce.com/downloads[^] 获得虚拟机镜像,下载完成后将它导入到你最爱的虚拟机软件中(我们将会使用 VirtualBox)。 在第一次启动机器后,它会询问你自定义三个 Linux 用户(`root`、`perforce` 与 `git`)的密码, 并且提供一个实例名字来区分在同一网络下不同的安装。当那些都完成后,将会看到这样: @@ -323,7 +323,7 @@ Git-p4 并不像 Git Fusion 一样灵活或完整,但是它允许你在无需 [NOTE] ====== 为了与 git-p4 一起工作需要在你的 `PATH` 环境变量中的某个目录中有 `p4` 工具。 -在写这篇文章的时候,它可以在 http://www.perforce.com/downloads/Perforce/20-User[] 免费获得。 +在写这篇文章的时候,它可以在 https://www.perforce.com/downloads/helix-command-line-client-p4[^] 免费获得。 ====== ====== 设置 @@ -522,10 +522,8 @@ $ git log --oneline --all --graph --decorate 结果恰如我们只是做了一次 `git push`,就像是应当实际发生的最接近的类比。 -注意在这个过程中每一个 Git 提交都会被转化为一个 Perforce 变更集。 -如果想要将它们压缩成为一个单独的提交,可以在运行 `git p4 submit` 前进行一次交互式变基。 -同样注意的是所有被转化为变更集的提交的 SHA-1 校验和都改变了, -这是因为 git-p4 在每一个转化的提交增加一行到提交注释结尾: +注意在这个过程中每一个 Git 提交都会被转化为一个 Perforce 变更集。如果想要将它们压缩成为一个单独的提交,可以在运行 `git p4 submit` 前进行一次交互式变基。 +同样注意的是所有被转化为变更集的提交的 SHA-1 校验和都改变了,这是因为 git-p4 在每一个转化的提交增加一行到提交注释结尾: [source,console] ---- diff --git a/book/09-git-and-other-scms/sections/client-svn.asc b/book/09-git-and-other-scms/sections/client-svn.asc index 2d497f8f..85c1be27 100644 --- a/book/09-git-and-other-scms/sections/client-svn.asc +++ b/book/09-git-and-other-scms/sections/client-svn.asc @@ -30,8 +30,6 @@ Subversion 只能有一个线性的历史,弄乱它很容易。 为了演示这个功能,需要一个有写入权限的典型 SVN 仓库。 如果想要拷贝这些例子,你必须获取一份可写入的 SVN 测试仓库的副本。 为了轻松地拷贝,可以使用 Subversion 自带的一个名为 `svnsync` 的工具。 -为了这些测试,我们在 Google Code 上创建了一个 `protobuf` 项目部分拷贝的新 -Subversion 仓库。`protobuf` 是一个将结构性数据编码用于网络传输的工具。 接下来,你需要先创建一个新的本地 Subversion 仓库: @@ -103,8 +101,8 @@ Checked out HEAD: ---- 这相当于运行了两个命令—— `git svn init` 以及紧接着的 `git svn fetch` ——你提供的 URL。 -这会花费一些时间。例如,如果测试项目只有 75 个左右的提交并且代码库并不是很大, -但是 Git 必须一次一个地检出一个版本同时单独地提交它。 +这会花费一些时间。 +例如,如果测试项目只有 75 个左右的提交并且代码库并不是很大,但是 Git 必须一次一个地检出一个版本同时单独地提交它。 对于有成百上千个提交的项目,这真的可能会花费几小时甚至几天来完成。 `-T trunk -b branches -t tags` 部分告诉 Git Subversion 仓库遵循基本的分支与标签惯例。 @@ -374,15 +372,11 @@ $ git branch opera remotes/origin/opera 现在,如果想要将你的 `opera` 分支合并入 `trunk`(你的 `master` 分支),可以用一个正常的 `git merge` 来这样做。 但是你需要通过 `-m` 来提供一个描述性的提交信息,否则合并信息会是没有用的 “Merge branch opera”。 -记住尽管使用的是 `git merge` 来做这个操作,而且合并可能会比在 Subversion 中更容易一些 -(因为 Git 会为你自动地检测合适的合并基础),但这并不是一个普通的 Git 合并提交。 -你不得不将这个数据推送回一个 Subversion 服务器,Subversion 服务器不支持那些跟踪多个父结点的提交; -所以,当推送完成后,它看起来会是一个将其他分支的所有提交压缩在一起的单独提交。 +记住尽管使用的是 `git merge` 来做这个操作,而且合并可能会比在 Subversion 中更容易一些(因为 Git 会为你自动地检测合适的合并基础),但这并不是一个普通的 Git 合并提交。 +你不得不将这个数据推送回一个 Subversion 服务器,Subversion 服务器不支持那些跟踪多个父结点的提交;所以,当推送完成后,它看起来会是一个将其他分支的所有提交压缩在一起的单独提交。 在合并一个分支到另一个分支后,你并不能像 Git 中那样轻松地回到原来的分支继续工作。 -你运行的 `dcommit` 命令会将哪个分支被合并进来的信息抹掉,所以后续的合并基础计算会是错的—— -`dcommit` 会使你的 `git merge` 结果看起来像是运行了 `git merge --squash`。 -不幸的是,没有一个好的方式来避免这种情形—— Subversion 无法存储这个信息, -所以当使用它做为服务器时你总是会被它的限制打垮。 +你运行的 `dcommit` 命令会将哪个分支被合并进来的信息抹掉,所以后续的合并基础计算会是错的—— `dcommit` 会使你的 `git merge` 结果看起来像是运行了 `git merge --squash`。 +不幸的是,没有一个好的方式来避免这种情形—— Subversion 无法存储这个信息,所以当使用它做为服务器时你总是会被它的限制打垮。 为了避免这些问题,应该在合并到主干后删除本地分支(本例中是 `opera`)。 ===== Subversion 命令 diff --git a/book/09-git-and-other-scms/sections/import-custom.asc b/book/09-git-and-other-scms/sections/import-custom.asc index e75d1b29..b11a1b42 100644 --- a/book/09-git-and-other-scms/sections/import-custom.asc +++ b/book/09-git-and-other-scms/sections/import-custom.asc @@ -3,12 +3,10 @@ (((git commands, fast-import))) (((Importing, from others))) -如果你的系统不是上述中的任何一个,你需要在线查找一个导入器——针对许多其他系统有很多高质量的导入器, -包括 CVS、Clear Case、Visual Source Safe,甚至是一个档案目录。 +如果你的系统不是上述中的任何一个,你需要在线查找一个导入器——针对许多其他系统有很多高质量的导入器,包括 CVS、Clear Case、Visual Source Safe,甚至是一个档案目录。 如果没有一个工具适合你,需要一个不知名的工具,或者需要更大自由度的自定义导入过程,应当使用 `git fast-import`。 这个命令从标准输入中读取简单指令来写入特定的 Git 数据。 -通过这种方式创建 Git 对象比运行原始 Git 命令或直接写入原始对象 -(查看 <> 了解更多内容)更容易些。 +通过这种方式创建 Git 对象比运行原始 Git 命令或直接写入原始对象(查看 <> 了解更多内容)更容易些。 通过这种方式你可以编写导入脚本,从你要导入的系统中读取必要数据,然后直接打印指令到标准输出。 然后可以运行这个程序并通过 `git fast-import` 重定向管道输出。 @@ -31,11 +29,9 @@ current 所有要做的就是告诉 `fast-import` 哪些内容是快照,哪个提交数据指向它们,以及它们进入的顺序。 你的策略是一次访问一个快照,然后用每个目录中的内容创建提交,并且将每一个提交与前一个连接起来。 -如同我们在 <> 里做的, -我们将会使用 Ruby 写这个,因为它是我们平常工作中使用的并且它很容易读懂。 +如同我们在 <> 里做的,我们将会使用 Ruby 写这个,因为它是我们平常工作中使用的并且它很容易读懂。 可以使用任何你熟悉的东西来非常轻松地写这个例子——它只需要将合适的信息打印到 `标准输出`。 -然而,如果你在 Windows 上,这意味着需要特别注意不要引入回车符到行尾—— -`git fast-import` 非常特别地只接受换行符(LF)而不是 Windows 使用的回车换行符(CRLF)。 +然而,如果你在 Windows 上,这意味着需要特别注意不要引入回车符到行尾—— `git fast-import` 非常特别地只接受换行符(LF)而不是 Windows 使用的回车换行符(CRLF)。 现在开始,需要进入目标目录中并识别每一个子目录,每一个都是你要导入为提交的快照。 要进入到每个子目录中并为导出它打印必要的命令。 @@ -208,7 +204,6 @@ $stdout.binmode 就是这样。 这是全部的脚本: - [source,ruby] ---- #!/usr/bin/env ruby diff --git a/book/09-git-and-other-scms/sections/import-hg.asc b/book/09-git-and-other-scms/sections/import-hg.asc index 8f2b7230..8f777ae1 100644 --- a/book/09-git-and-other-scms/sections/import-hg.asc +++ b/book/09-git-and-other-scms/sections/import-hg.asc @@ -37,12 +37,10 @@ Bob Jones Joe Smith ---- -在这个例子中,同一个人(Bob)使用不同的名字创建变更集,其中一个实际上是正确的, -另一个完全不符合 Git 提交的规范。hg-fast-export 通过对每一行应用规则 -`""=""` ,将 `` 映射到 `` 来修正这个问题。 -在 `` 和 `` 字符串中,所有 Python 的 `string_escape` -支持的转义序列都会被解释。如果作者映射文件中并不包含匹配的 ``, -那么该作者将原封不动地被发送到 Git。 +在这个例子中,同一个人(Bob)使用不同的名字创建变更集,其中一个实际上是正确的,另一个完全不符合 Git 提交的规范。 +hg-fast-export 通过对每一行应用规则 `""=""` ,将 `` 映射到 `` 来修正这个问题。 +在 `` 和 `` 字符串中,所有 Python 的 `string_escape` 支持的转义序列都会被解释。 +如果作者映射文件中并不包含匹配的 ``,那么该作者将原封不动地被发送到 Git。 如果所有的用户名看起来都是正确的,那我们根本就不需要这个文件。 在本例中,我们会使文件看起来像这样: @@ -65,9 +63,8 @@ $ cd /tmp/converted $ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors ---- -`-r` 选项告诉 hg-fast-export 去哪里寻找我们想要转换的 Mercurial 仓库,`-A` -标记告诉它在哪找到作者映射文件(分支和标签的映射文件分别通过 `-B` 和 `-T` 选项来指定)。 -这个脚本会分析 Mercurial 变更集然后将它们转换成 Git“fast-import”功能(我们将在之后详细讨论)需要的脚本。 +`-r` 选项告诉 hg-fast-export 去哪里寻找我们想要转换的 Mercurial 仓库,`-A` 标记告诉它在哪找到作者映射文件(分支和标签的映射文件分别通过 `-B` 和 `-T` 选项来指定)。 +这个脚本会分析 Mercurial 变更集然后将它们转换成 Git 的“fast-import”功能(我们将在之后详细讨论)需要的脚本。 这会花一点时间(尽管它比通过网格 _更_ 快),输出相当的冗长: [source,console] diff --git a/book/09-git-and-other-scms/sections/import-svn.asc b/book/09-git-and-other-scms/sections/import-svn.asc index 098c61d3..0ef98589 100644 --- a/book/09-git-and-other-scms/sections/import-svn.asc +++ b/book/09-git-and-other-scms/sections/import-svn.asc @@ -32,14 +32,14 @@ $ svn log --xml --quiet | grep author | sort -u | \ [NOTE] ==== -如果你在 Windows 上运行它,那么到这里就会遇到问题。微软提供了一些不错的建议和示例: -https://docs.microsoft.com/en-us/azure/devops/repos/git/perform-migration-from-svn-to-git[]. +如果你在 Windows 上运行它,那么到这里就会遇到问题。 +微软提供了一些不错的建议和示例:https://learn.microsoft.com/zh-cn/azure/devops/repos/git/perform-migration-from-svn-to-git[^]. ==== 你可以将此文件提供给 `git svn` 来帮助它更加精确地映射作者数据。 -也可以通过传递 `--no-metadata` 给 `clone` 与 `init` 命令,告诉 `git svn` 不要包括 -Subversion 通常会导入的元数据。在导入过程中,Git 会在每个提交说明的元数据中生成一个 -`git-svn-id`。 +也可以通过传递 `--no-metadata` 给 `clone` 与 `init` 命令,告诉 `git svn` 不要包括 Subversion 通常会导入的元数据。 +在导入过程中,Git 会在每个提交说明的元数据中生成一个 `git-svn-id`。 +这会使你的 Git 日志变得膨胀臃肿,并可能使其变得有些不够清晰。 [NOTE] ==== @@ -104,7 +104,10 @@ $ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do $ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done ---- -你可能会看到一些额外的分支,这些分支的后缀是 `@xxx` (其中 xxx 是一个数字),而在 Subversion 中你只会看到一个分支。这实际上是 Subversion 一个叫做“peg-revisions”的功能,Git在语法上没有与之对应的功能。因此, `git svn` 只是简单地将 SVN peg-revision 版本号添加到分支名称中,这同你在 SVN 中修改分支名称来定位一个分支的“peg-revision”是一样的。如果你对于 peg-revisions 完全不在乎,通过下面的命令可以轻易地移除他们: +你可能会看到一些额外的分支,这些分支的后缀是 `@xxx` (其中 xxx 是一个数字),而在 Subversion 中你只会看到一个分支。 +这实际上是 Subversion 一个叫做“peg-revisions”的功能,Git在语法上没有与之对应的功能。 +因此, `git svn` 只是简单地将 SVN peg-revision 版本号添加到分支名称中,这同你在 SVN 中修改分支名称来定位一个分支的“peg-revision”是一样的。 +如果你对于 peg-revisions 完全不在乎,通过下面的命令可以轻易地移除他们: [source,console] ---- @@ -113,8 +116,8 @@ $ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git bran 现在所有的旧分支都是真正的 Git 分支,并且所有的旧标签都是真正的 Git 标签。 -还有最后一点东西需要清理。`git svn` 会创建一个名为 `trunk` 的额外分支,它对应于 -Subversion 的默认分支,然而 `trunk` 引用和 `master` 指向同一个位置。 +还有最后一点东西需要清理。 +`git svn` 会创建一个名为 `trunk` 的额外分支,它对应于 Subversion 的默认分支,然而 `trunk` 引用和 `master` 指向同一个位置。 鉴于在 Git 中 `master` 最为常用,因此我们可以移除额外的分支: [source,console] diff --git a/book/10-git-internals/sections/environment.asc b/book/10-git-internals/sections/environment.asc index 3a22fb28..7a42a182 100644 --- a/book/10-git-internals/sections/environment.asc +++ b/book/10-git-internals/sections/environment.asc @@ -4,22 +4,21 @@ Git 总是在一个 `bash` shell 中运行,并借助一些 shell 环境变量 有时候,知道它们是什么以及它们如何让 Git 按照你想要的方式去运行会很有用。 这里不会列出所有的 Git 环境变量,但我们会涉及最有用的那部分。 - ==== 全局行为 像通常的程序一样,Git 的常规行为依赖于环境变量。 -*`GIT_EXEC_PATH`* 决定 Git 到哪找它的子程序 (像 `git-commit`, `git-diff` 等等)。 - 你可以用 `git --exec-path` 来查看当前设置。 +*`GIT_EXEC_PATH`* 决定 Git 到哪找它的子程序(像 `git-commit`, `git-diff` 等等)。 +你可以用 `git --exec-path` 来查看当前设置。 通常不会考虑修改 *`HOME`* 这个变量(太多其它东西都依赖它),这是 Git 查找全局配置文件的地方。 - 如果你想要一个包括全局配置的真正的便携版 Git, 你可以在便携版 Git 的 shell 配置中覆盖 `HOME` 设置。 +如果你想要一个包括全局配置的真正的便携版 Git, 你可以在便携版 Git 的 shell 配置中覆盖 `HOME` 设置。 *`PREFIX`* 也类似,除了用于系统级别的配置。 - Git 在 `$PREFIX/etc/gitconfig` 查找此文件。 +Git 在 `$PREFIX/etc/gitconfig` 查找此文件。 如果设置了 *`GIT_CONFIG_NOSYSTEM`*,就禁用系统级别的配置文件。 - 这在系统配置影响了你的命令,而你又无权限修改的时候很有用。 +这在系统配置影响了你的命令,而你又无权限修改的时候很有用。 *`GIT_PAGER`* 控制在命令行上显示多页输出的程序。 如果这个没有设置,就会用 `PAGER` 。 @@ -27,7 +26,6 @@ Git 总是在一个 `bash` shell 中运行,并借助一些 shell 环境变量 *`GIT_EDITOR`* 当用户需要编辑一些文本(比如提交信息)时, Git 会启动这个编辑器。 如果没设置,就会用 `EDITOR` 。 - ==== 版本库位置 Git 用了几个变量来确定它如何与当前版本库交互。 @@ -39,8 +37,7 @@ Git 用了几个变量来确定它如何与当前版本库交互。 如果你访问加载很慢的目录(如那些磁带机上的或通过网络连接访问的),你可能会想让 Git 早点停止尝试,尤其是 shell 构建时调用了 Git 。 *`GIT_WORK_TREE`* 是非空版本库的工作目录的根路径。 -如果指定了 `--git-dir` 或 `GIT_DIR` 但未指定 `--work-tree`、`GIT_WORK_TREE` 或 -`core.worktree`,那么当前工作目录就会视作工作树的顶级目录。 +如果指定了 `--git-dir` 或 `GIT_DIR` 但未指定 `--work-tree`、`GIT_WORK_TREE` 或 `core.worktree`,那么当前工作目录就会视作工作树的顶级目录。 *`GIT_INDEX_FILE`* 是索引文件的路径(只有非空版本库有)。 @@ -49,21 +46,19 @@ Git 用了几个变量来确定它如何与当前版本库交互。 *`GIT_ALTERNATE_OBJECT_DIRECTORIES`* 一个冒号分割的列表(格式类似 `/dir/one:/dir/two:…`)用来告诉 Git 到哪里去找不在 `GIT_OBJECT_DIRECTORY` 目录中的对象。 如果你有很多项目有相同内容的大文件,这个可以用来避免存储过多备份。 - ==== 路径规则 所谓 “pathspec” 是指你在 Git 中如何指定路径,包括通配符的使用。 它们会在 `.gitignore` 文件中用到,命令行里也会用到(`git add *.c`)。 *`GIT_GLOB_PATHSPECS`* 和 *`GIT_NOGLOB_PATHSPECS`* 控制通配符在路径规则中的默认行为。 -如果 `GIT_GLOB_PATHSPECS` 设置为 1, 通配符表现为通配符(这是默认设置); 如果 `GIT_NOGLOB_PATHSPECS` 设置为 1,通配符仅匹配字面。意思是 `+*.c+` 只会匹配 _文件名是_ “*.c” 的文件,而不是以 `.c` 结尾的文件。 +如果 `GIT_GLOB_PATHSPECS` 设置为 1, 通配符表现为通配符(这是默认设置);如果 `GIT_NOGLOB_PATHSPECS` 设置为 1,通配符仅匹配字面。意思是 `+*.c+` 只会匹配 _文件名是_ “*.c” 的文件,而不是以 `.c` 结尾的文件。 你可以在各个路径规范中用 `:(glob)` 或 `:(literal)` 开头来覆盖这个配置,如 `+:(glob)*.c+` 。 *`GIT_LITERAL_PATHSPECS`* 禁用上面的两种行为;通配符将不能用,前缀覆盖也不能用。 *`GIT_ICASE_PATHSPECS`* 让所有的路径规范忽略大小写。 - ==== 提交 Git 提交对象的创建通常最后是由 `git-commit-tree` 来完成, `git-commit-tree` 用这些环境变量作主要的信息源。 仅当这些值不存在才回退到预置的值。 @@ -83,7 +78,6 @@ Git 提交对象的创建通常最后是由 `git-commit-tree` 来完成, `git- 如果 `user.email` 没有配置, 就会用到 *`EMAIL`* 指定的邮件地址。 如果 _这个_ 也没有设置, Git 继续回退使用系统用户和主机名。 - ==== 网络 Git 使用 `curl` 库通过 HTTP 来完成网络操作, 所以 *`GIT_CURL_VERBOSE`* 告诉 Git 显示所有由那个库产生的消息。 @@ -92,14 +86,12 @@ Git 使用 `curl` 库通过 HTTP 来完成网络操作, 所以 *`GIT_CURL_VERB *`GIT_SSL_NO_VERIFY`* 告诉 Git 不用验证 SSL 证书。 这在有些时候是需要的, 例如你用一个自己签名的证书通过 HTTPS 来提供 Git 服务, 或者你正在搭建 Git 服务器,还没有安装完全的证书。 - 如果 Git 操作在网速低于 *`GIT_HTTP_LOW_SPEED_LIMIT`* 字节/秒,并且持续 *`GIT_HTTP_LOW_SPEED_TIME`* 秒以上的时间,Git 会终止那个操作。 这些值会覆盖 `http.lowSpeedLimit` 和 `http.lowSpeedTime` 配置的值。 *`GIT_HTTP_USER_AGENT`* 设置 Git 在通过 HTTP 通讯时用到的 user-agent。 默认值类似于 `git/2.0.0` 。 - ==== 比较和合并 *`GIT_DIFF_OPTS`* 这个有点起错名字了。 @@ -217,12 +209,14 @@ nothing to commit, working directory clean 如果指定了 *`GIT_SSH`*, Git 连接 SSH 主机时会用指定的程序代替 `ssh` 。 它会被用 `$GIT_SSH [username@]host [-p ] ` 的命令方式调用。 -这不是配置定制 `ssh` 调用方式的最简单的方法; 它不支持额外的命令行参数, 所以你必须写一个封装脚本然后让 `GIT_SSH` 指向它。 -可能用 `~/.ssh/config` 会更简单。 +注意,这不是配置定制 `ssh` 调用方式的最简单的方法; 它不支持额外的命令行参数。 +如果需要支持额外命令行参数,您可以使用 *`GIT_SSH_COMMAND`* 编写一个封装脚本(Wrapper script)并将 GIT_SSH 指向该脚本,可能用 `~/.ssh/config` 会更简单。 + +*`GIT_SSH_COMMAND`* 用于设置 Git 试图连接 SSH 主机时所使用的 SSH 命令。 +该命令会由 Shell 解释执行,并且允许向 `ssh` 传入额外的命令行参数,例如:`GIT_SSH_COMMAND="ssh -i ~/.ssh/my_key" git clone git@example.com:my/repo`。 *`GIT_ASKPASS`* 覆盖了 `core.askpass` 配置。 -这是 Git 需要向用户请求验证时用到的程序,它接受一个文本提示作为命令行参数,并在 `stdout` 中返回应答。 -(查看 <> 访问更多相关内容) +这是 Git 需要向用户请求验证时用到的程序,它接受一个文本提示作为命令行参数,并在 `stdout` 中返回应答。(查看 <> 访问更多相关内容) *`GIT_NAMESPACE`* 控制有命令空间的引用的访问,与 `--namespace` 标志是相同的。 这主要在服务器端有用, 如果你想在一个版本库中存储单个版本库的多个 fork, 只要保持引用是隔离的就可以。 diff --git a/book/10-git-internals/sections/maintenance.asc b/book/10-git-internals/sections/maintenance.asc index 472da33f..a40080e2 100644 --- a/book/10-git-internals/sections/maintenance.asc +++ b/book/10-git-internals/sections/maintenance.asc @@ -9,8 +9,7 @@ Git 会不定时地自动运行一个叫做 “auto gc” 的命令。 大多数时候,这个命令并不会产生效果。 然而,如果有太多松散对象(不在包文件中的对象)或者太多包文件,Git 会运行一个完整的 `git gc` 命令。 -“gc” 代表垃圾回收,这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中, -将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。 +“gc” 代表垃圾回收,这个命令会做以下事情:收集所有松散对象并将它们放置到包文件中,将多个包文件合并为一个大的包文件,移除与任何提交都不相关的陈旧对象。 可以像下面一样手动执行自动垃圾回收: @@ -60,8 +59,7 @@ cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0 ==== 数据恢复 在你使用 Git 的时候,你可能会意外丢失一次提交。 -通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支, -亦或者硬重置了一个分支,放弃了你想要的提交。 +通常这是因为你强制删除了正在工作的分支,但是最后却发现你还需要这个分支,亦或者硬重置了一个分支,放弃了你想要的提交。 如果这些事情已经发生,该如何找回你的提交呢? 下面的例子将硬重置你的测试仓库中的 `master` 分支到一个旧的提交,以此来恢复丢失的提交。 @@ -96,8 +94,7 @@ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 最方便,也是最常用的方法,是使用一个名叫 `git reflog` 的工具。 当你正在工作时,Git 会默默地记录每一次你改变 HEAD 时它的值。 每一次你提交或改变分支,引用日志都会被更新。 -引用日志(reflog)也可以通过 `git update-ref` 命令更新,我们在 -<> 有提到使用这个命令而不是是直接将 SHA-1 的值写入引用文件中的原因。 +引用日志(reflog)也可以通过 `git update-ref` 命令更新,我们在 <> 有提到使用这个命令而不是是直接将 SHA-1 的值写入引用文件中的原因。 你可以在任何时候通过执行 `git reflog` 命令来了解你曾经做过什么: [source,console] @@ -145,9 +142,8 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- -不错,现在有一个名为 `recover-branch` 的分支是你的 `master` 分支曾经指向的地方, -再一次使得前两次提交可到达了。接下来,假设你丢失的提交因为某些原因不在引用日志中, -那么我们可以通过移除 `recover-branch` 分支并删除引用日志来模拟这种情况。 +不错,现在有一个名为 `recover-branch` 的分支是你的 `master` 分支曾经指向的地方,再一次使得前两次提交可到达了。 +接下来,假设你丢失的提交因为某些原因不在引用日志中,那么我们可以通过移除 `recover-branch` 分支并删除引用日志来模拟这种情况。 现在前两次提交又不被任何分支指向了: [source,console] @@ -189,8 +185,7 @@ Git 有很多很棒的功能,但是其中一个特性会导致问题,`git cl *警告:这个操作对提交历史的修改是破坏性的。* 它会从你必须修改或移除一个大文件引用最早的树对象开始重写每一次提交。 -如果你在导入仓库后,在任何人开始基于这些提交工作前执行这个操作,那么将不会有任何问题——否则, -你必须通知所有的贡献者他们需要将他们的成果变基到你的新提交上。 +如果你在导入仓库后,在任何人开始基于这些提交工作前执行这个操作,那么将不会有任何问题——否则,你必须通知所有的贡献者他们需要将他们的成果变基到你的新提交上。 为了演示,我们将添加一个大文件到测试仓库中,并在下一次提交中删除它,现在我们需要找到它,并将它从仓库中永久删除。 首先,添加一个大文件到仓库中: @@ -253,8 +248,7 @@ size-garbage: 0 首先你必须找到它。 在本例中,你已经知道是哪个文件了。 但是假设你不知道;该如何找出哪个文件或哪些文件占用了如此多的空间? -如果你执行 `git gc` 命令,所有的对象将被放入一个包文件中,你可以通过运行 `git verify-pack` 命令, -然后对输出内容的第三列(即文件大小)进行排序,从而找出这个大文件。 +如果你执行 `git gc` 命令,所有的对象将被放入一个包文件中,你可以通过运行 `git verify-pack` 命令,然后对输出内容的第三列(即文件大小)进行排序,从而找出这个大文件。 你也可以将这个命令的执行结果通过管道传送给 `tail` 命令,因为你只需要找到列在最后的几个大对象。 [source,console] @@ -300,8 +294,7 @@ Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2) Ref 'refs/heads/master' was rewritten ---- -`--index-filter` 选项类似于在 <> 中提到的 `--tree-filter` 选项, -不过这个选项并不会让命令将修改在硬盘上检出的文件,而只是修改在暂存区或索引中的文件。 +`--index-filter` 选项类似于在 <> 中提到的 `--tree-filter` 选项,不过这个选项并不会让命令将修改在硬盘上检出的文件,而只是修改在暂存区或索引中的文件。 你必须使用 `git rm --cached` 命令来移除文件,而不是通过类似 `rm file` 的命令——因为你需要从索引中移除它,而不是磁盘中。 还有一个原因是速度—— Git 在运行过滤器时,并不会检出每个修订版本到磁盘中,所以这个过程会非常快。 @@ -311,8 +304,7 @@ Ref 'refs/heads/master' was rewritten 否则,这个命令会从最旧的提交开始,这将会花费许多不必要的时间。 你的历史中将不再包含对那个文件的引用。 -不过,你的引用日志和你在 `.git/refs/original` 通过 `filter-branch` -选项添加的新引用中还存有对这个文件的引用,所以你必须移除它们然后重新打包数据库。 +不过,你的引用日志和你在 `.git/refs/original` 通过 `filter-branch` 选项添加的新引用中还存有对这个文件的引用,所以你必须移除它们然后重新打包数据库。 在重新打包前需要移除任何包含指向那些旧提交的指针的文件: [source,console] diff --git a/book/10-git-internals/sections/objects.asc b/book/10-git-internals/sections/objects.asc index cc1cb1d7..93abd042 100644 --- a/book/10-git-internals/sections/objects.asc +++ b/book/10-git-internals/sections/objects.asc @@ -1,12 +1,13 @@ [[_objects]] === Git 对象 -Git 是一个内容寻址文件系统,听起来很酷。但这是什么意思呢? +Git 是一个内容寻址文件系统, +听起来很酷。 +但这是什么意思呢? 这意味着,Git 的核心部分是一个简单的键值对数据库(key-value data store)。 你可以向 Git 仓库中插入任意类型的内容,它会返回一个唯一的键,通过该键可以在任意时刻再次取回该内容。 -可以通过底层命令 `git hash-object` 来演示上述效果——该命令可将任意数据保存于 -`.git/objects` 目录(即 *对象数据库*),并返回指向该数据对象的唯一的键。 +可以通过底层命令 `git hash-object` 来演示上述效果——该命令可将任意数据保存于 `.git/objects` 目录(即 *对象数据库*),并返回指向该数据对象的唯一的键。 首先,我们需要初始化一个新的 Git 版本库,并确认 `objects` 目录为空: @@ -46,8 +47,7 @@ $ find .git/objects -type f ---- 如果你再次查看 `objects` 目录,那么可以在其中找到一个与新内容对应的文件。 -这就是开始时 Git 存储内容的方式——一个文件对应一条内容, -以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 +这就是开始时 Git 存储内容的方式——一个文件对应一条内容,以该内容加上特定头部信息一起的 SHA-1 校验和为文件命名。 校验和的前两个字符用于命名子目录,余下的 38 个字符则用作文件名。 一旦你将内容存储在了对象数据库中,那么可以通过 `cat-file` 命令从 Git 那里取回数据。 @@ -161,13 +161,11 @@ $ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 image::images/data-model-1.png[简化版的 Git 数据模型。] 你可以轻松创建自己的树对象。 -通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象, -如此重复便可依次记录(某个时间段内)一系列的树对象。 +通常,Git 根据某一时刻暂存区(即 index 区域,下同)所表示的状态创建并记录一个对应的树对象,如此重复便可依次记录(某个时间段内)一系列的树对象。 因此,为创建一个树对象,首先需要通过暂存一些文件来创建一个暂存区。 可以通过底层命令 `git update-index` 为一个单独文件——我们的 test.txt 文件的首个版本——创建一个暂存区。 利用该命令,可以把 `test.txt` 文件的首个版本人为地加入一个新的暂存区。 -必须为上述命令指定 `--add` 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢); -同样必需的还有 `--cacheinfo` 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 +必须为上述命令指定 `--add` 选项,因为此前该文件并不在暂存区中(我们甚至都还没来得及创建一个暂存区呢);同样必需的还有 `--cacheinfo` 选项,因为将要添加的文件位于 Git 数据库中,而不是位于当前目录下。 同时,需要指定文件模式、SHA-1 与文件名: [source,console] @@ -178,12 +176,10 @@ $ git update-index --add --cacheinfo 100644 \ 本例中,我们指定的文件模式为 `100644`,表明这是一个普通文件。 其他选择包括:`100755`,表示一个可执行文件;`120000`,表示一个符号链接。 -这里的文件模式参考了常见的 UNIX 文件模式,但远没那么灵活——上述三种模式即是 -Git 文件(即数据对象)的所有合法模式(当然,还有其他一些模式,但用于目录项和子模块)。 +这里的文件模式参考了常见的 UNIX 文件模式,但远没那么灵活——上述三种模式即是 Git 文件(即数据对象)的所有合法模式(当然,还有其他一些模式,但用于目录项和子模块)。 现在,可以通过 `git write-tree` 命令将暂存区内容写入一个树对象。 -此处无需指定 `-w` 选项——如果某个树对象此前并不存在的话,当调用此命令时, -它会根据当前暂存区状态自动创建一个新的树对象: +此处无需指定 `-w` 选项——如果某个树对象此前并不存在的话,当调用此命令时,它会根据当前暂存区状态自动创建一个新的树对象: [source,console] ---- @@ -239,8 +235,7 @@ $ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614 100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt ---- -如果基于这个新的树对象创建一个工作目录,你会发现工作目录的根目录包含两个文件以及一个名为 -`bak` 的子目录,该子目录包含 `test.txt` 文件的第一个版本。 +如果基于这个新的树对象创建一个工作目录,你会发现工作目录的根目录包含两个文件以及一个名为 `bak` 的子目录,该子目录包含 `test.txt` 文件的第一个版本。 可以认为 Git 内部存储着的用于表示上述结构的数据是这样的: .当前 Git 的数据内容结构。 @@ -249,8 +244,7 @@ image::images/data-model-2.png[当前 Git 的数据内容结构。] [[_git_commit_objects]] ==== 提交对象 -如果你做完了以上所有操作,那么现在就有了三个树对象,分别代表我们想要跟踪的不同项目快照。 -然而问题依旧:若想重用这些快照,你必须记住所有三个 SHA-1 哈希值。 +如果你做完了以上所有操作,那么现在就有了三个树对象,分别代表我们想要跟踪的不同项目快照。然而问题依旧:若想重用这些快照,你必须记住所有三个 SHA-1 哈希值。 并且,你也完全不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。 而以上这些,正是提交对象(commit object)能为你保存的基本信息。 @@ -263,8 +257,13 @@ $ echo 'first commit' | git commit-tree d8329f fdf4fc3344e67ab068f836878b6c4951e3b15f3d ---- +[NOTE] +==== 由于创建时间和作者数据不同,你现在会得到一个不同的散列值。 +此外,虽然原则上只要拥有这些数据,任何提交对象(Commit object)都可以被精准复现,但鉴于本书构建过程中的一些历史细节,书中所打印的提交哈希值可能与实际生成的提交并不一致。 请将本章后续内容中的提交和标签的散列值替换为你自己的校验和。 +==== + 现在可以通过 `git cat-file` 命令查看这个新提交对象: [source,console] @@ -277,10 +276,7 @@ committer Scott Chacon 1243040974 -0700 first commit ---- -提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照; -然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交); -之后是作者/提交者信息(依据你的 `user.name` 和 `user.email` 配置来设定,外加一个时间戳); -留空一行,最后是提交注释。 +提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交);之后是作者/提交者信息(依据你的 `user.name` 和 `user.email` 配置来设定,外加一个时间戳);留空一行,最后是提交注释。 接着,我们将创建另两个提交对象,它们分别引用各自的上一个提交(作为其父提交对象): @@ -329,8 +325,7 @@ Date: Fri May 22 18:09:34 2009 -0700 太神奇了: 就在刚才,你没有借助任何上层命令,仅凭几个底层操作便完成了一个 Git 提交历史的创建。 -这就是每次我们运行 `git add` 和 `git commit` 命令时,Git 所做的工作实质就是将被改写的文件保存为数据对象, -更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 +这就是每次我们运行 `git add` 和 `git commit` 命令时,Git 所做的工作实质就是将被改写的文件保存为数据对象,更新暂存区,记录树对象,最后创建一个指明了顶层树对象和父提交的提交对象。 这三种主要的 Git 对象——数据对象、树对象、提交对象——最初均以单独文件的形式保存在 `.git/objects` 目录下。 下面列出了目前示例目录内的所有对象,辅以各自所保存内容的注释: @@ -379,8 +374,7 @@ Git 首先会以识别出的对象的类型作为开头来构造一个头部信 ---- Git 会将上述头部信息和原始数据拼接起来,并计算出这条新内容的 SHA-1 校验和。 -在 Ruby 中可以这样计算 SHA-1 值——先通过 `require` 命令导入 SHA-1 digest 库, -然后对目标字符串调用 `Digest::SHA1.hexdigest()`: +在 Ruby 中可以这样计算 SHA-1 值——先通过 `require` 命令导入 SHA-1 digest 库,然后对目标字符串调用 `Digest::SHA1.hexdigest()`: [source,console] ---- diff --git a/book/10-git-internals/sections/packfiles.asc b/book/10-git-internals/sections/packfiles.asc index be2e211c..be8a182e 100644 --- a/book/10-git-internals/sections/packfiles.asc +++ b/book/10-git-internals/sections/packfiles.asc @@ -1,7 +1,6 @@ === 包文件 -如果你跟着做完了上一节中的所有步骤,那么现在应该有了一个测试用的 Git 仓库, -其中包含 11 个对象:四个数据对象,三个树对象,三个提交对象和一个标签对象: +如果你跟着做完了上一节中的所有步骤,那么现在应该有了一个测试用的 Git 仓库,其中包含 11 个对象:四个数据对象,三个树对象,三个提交对象和一个标签对象: [source,console] ---- diff --git a/book/10-git-internals/sections/plumbing-porcelain.asc b/book/10-git-internals/sections/plumbing-porcelain.asc index 72c93dd6..efe32c86 100644 --- a/book/10-git-internals/sections/plumbing-porcelain.asc +++ b/book/10-git-internals/sections/plumbing-porcelain.asc @@ -2,18 +2,14 @@ === 底层命令与上层命令 本书主要涵盖了 `checkout`、`branch`、`remote` 等约 30 个 Git 的子命令。 -然而,由于 Git 最初是一套面向版本控制系统的工具集,而不是一个完整的、用户友好的版本控制系统, -所以它还包含了一部分用于完成底层工作的子命令。 -这些命令被设计成能以 UNIX 命令行的风格连接在一起,抑或藉由脚本调用,来完成工作。 +然而,由于 Git 最初是一套面向版本控制系统的工具集,而不是一个完整的、用户友好的版本控制系统,所以它还包含了一部分用于完成底层工作的子命令。这些命令被设计成能以 UNIX 命令行的风格连接在一起,抑或藉由脚本调用,来完成工作。 这部分命令一般被称作“底层(plumbing)”命令,而那些更友好的命令则被称作“上层(porcelain)”命令。 你或许已经注意到了,本书前九章专注于探讨上层命令。 -然而在本章中,我们将主要面对底层命令。 -因为,底层命令得以让你窥探 Git 内部的工作机制,也有助于说明 Git 是如何完成工作的,以及它为何如此运作。 +然而在本章中,我们将主要面对底层命令。因为,底层命令得以让你窥探 Git 内部的工作机制,也有助于说明 Git 是如何完成工作的,以及它为何如此运作。 多数底层命令并不面向最终用户:它们更适合作为新工具的组件和自定义脚本的组成部分。 -当在一个新目录或已有目录执行 `git init` 时,Git 会创建一个 `.git` 目录。 -这个目录包含了几乎所有 Git 存储和操作的东西。 +当在一个新目录或已有目录执行 `git init` 时,Git 会创建一个 `.git` 目录。这个目录包含了几乎所有 Git 存储和操作的东西。 如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。 本章探讨的所有内容,均位于这个目录内。 新初始化的 `.git` 目录的典型结构如下: @@ -30,17 +26,12 @@ objects/ refs/ ---- -随着 Git 版本的不同,该目录下可能还会包含其他内容。 -不过对于一个全新的 `git init` 版本库,这将是你看到的默认结构。 +随着 Git 版本的不同,该目录下可能还会包含其他内容。不过对于一个全新的 `git init` 版本库,这将是你看到的默认结构。 `description` 文件仅供 GitWeb 程序使用,我们无需关心。 -`config` 文件包含项目特有的配置选项。 -`info` 目录包含一个全局性排除(global exclude)文件(((excludes))), -用以放置那些不希望被记录在 `.gitignore` 文件中的忽略模式(ignored patterns)。 -`hooks` 目录包含客户端或服务端的钩子脚本(hook scripts), -在 <> 中这部分话题已被详细探讨过。 +`config` 文件包含项目特有的配置选项。`info` 目录包含一个全局性排除(global exclude)文件(((excludes))),用以放置那些不希望被记录在 `.gitignore` 文件中的忽略模式(ignored patterns)。 +`hooks` 目录包含客户端或服务端的钩子脚本(hook scripts),在 <> 中这部分话题已被详细探讨过。 剩下的四个条目很重要:`HEAD` 文件、(尚待创建的)`index` 文件,和 `objects` 目录、`refs` 目录。 它们都是 Git 的核心组成部分。 -`objects` 目录存储所有数据内容;`refs` 目录存储指向数据(分支、远程仓库和标签等)的提交对象的指针; -`HEAD` 文件指向目前被检出的分支;`index` 文件保存暂存区信息。 +`objects` 目录存储所有数据内容;`refs` 目录存储指向数据(分支、远程仓库和标签等)的提交对象的指针;`HEAD` 文件指向目前被检出的分支;`index` 文件保存暂存区信息。 我们将详细地逐一检视这四部分,来理解 Git 是如何运转的。 diff --git a/book/10-git-internals/sections/refs.asc b/book/10-git-internals/sections/refs.asc index 2791b40d..2ace2670 100644 --- a/book/10-git-internals/sections/refs.asc +++ b/book/10-git-internals/sections/refs.asc @@ -1,13 +1,10 @@ [[_git_refs]] === Git 引用 -如果你对仓库中从一个提交(比如 `1a410e`)开始往前的历史感兴趣,那么可以运行 -`git log 1a410e` 这样的命令来显示历史,不过你需要记得 `1a410e` 是你查看历史的起点提交。 -如果我们有一个文件来保存 SHA-1 值,而该文件有一个简单的名字, -然后用这个名字指针来替代原始的 SHA-1 值的话会更加简单。 +如果你对仓库中从一个提交(比如 `1a410e`)开始往前的历史感兴趣,那么可以运行 `git log 1a410e` 这样的命令来显示历史,不过你需要记得 `1a410e` 是你查看历史的起点提交。 +如果我们有一个文件来保存 SHA-1 值,而该文件有一个简单的名字,然后用这个名字指针来替代原始的 SHA-1 值的话会更加简单。 -在 Git 中,这种简单的名字被称为“引用(references,或简写为 refs)”。 -你可以在 `.git/refs` 目录下找到这类含有 SHA-1 值的文件。 +在 Git 中,这种简单的名字被称为“引用(references,或简写为 refs)”。你可以在 `.git/refs` 目录下找到这类含有 SHA-1 值的文件。 在目前的项目中,这个目录没有包含任何文件,但它包含了一个简单的目录结构: [source,console] @@ -36,8 +33,7 @@ cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit ---- -我们不提倡直接编辑引用文件。 -如果想更新某个引用,Git 提供了一个更加安全的命令 `update-ref` 来完成此事: +我们不提倡直接编辑引用文件。如果想更新某个引用,Git 提供了一个更加安全的命令 `update-ref` 来完成此事: [source,console] ---- @@ -66,8 +62,7 @@ fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit .包含分支引用的 Git 目录对象。 image::images/data-model-4.png[包含分支引用的 Git 目录对象。] -当运行类似于 `git branch ` 这样的命令时,Git 实际上会运行 `update-ref` 命令, -取得当前所在分支最新提交对应的 SHA-1 值,并将其加入你想要创建的任何新引用中。 +当运行类似于 `git branch ` 这样的命令时,Git 实际上会运行 `update-ref` 命令,取得当前所在分支最新提交对应的 SHA-1 值,并将其加入你想要创建的任何新引用中。 [[ref_the_ref]] ==== HEAD 引用 @@ -79,8 +74,7 @@ HEAD 文件通常是一个符号引用(symbolic reference),指向目前所 所谓符号引用,表示它是一个指向其他引用的指针。 然而在某些罕见的情况下,HEAD 文件可能会包含一个 git 对象的 SHA-1 值。 -当你在检出一个标签、提交或远程分支,让你的仓库变成 -https://git-scm.com/docs/git-checkout#_detached_head[“分离 HEAD”]状态时,就会出现这种情况。 +当你在检出一个标签、提交或远程分支,让你的仓库变成 https://git-scm.com/docs/git-checkout#_detached_head[“分离 HEAD”]状态时,就会出现这种情况。 如果查看 HEAD 文件的内容,通常我们看到类似这样的内容: diff --git a/book/10-git-internals/sections/refspec.asc b/book/10-git-internals/sections/refspec.asc index a1964d9c..236eb59c 100644 --- a/book/10-git-internals/sections/refspec.asc +++ b/book/10-git-internals/sections/refspec.asc @@ -9,8 +9,7 @@ $ git remote add origin https://github.com/schacon/simplegit-progit ---- -运行上述命令会在你仓库中的 `.git/config` 文件中添加一个小节, -并在其中指定远程版本库的名称(`origin`)、URL 和一个用于获取操作的 *引用规范(refspec)*: +运行上述命令会在你仓库中的 `.git/config` 文件中添加一个小节,并在其中指定远程版本库的名称(`origin`)、URL 和一个用于获取操作的 *引用规范(refspec)*: [source,ini] ---- @@ -19,13 +18,10 @@ $ git remote add origin https://github.com/schacon/simplegit-progit fetch = +refs/heads/*:refs/remotes/origin/* ---- -引用规范的格式由一个可选的 `+` 号和紧随其后的 `:` 组成, -其中 `` 是一个模式(pattern),代表远程版本库中的引用; -`` 是本地跟踪的远程引用的位置。 +引用规范的格式由一个可选的 `+` 号和紧随其后的 `:` 组成,其中 `` 是一个模式(pattern),代表远程版本库中的引用;`` 是本地跟踪的远程引用的位置。 `+` 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。 -默认情况下,引用规范由 `git remote add origin` 命令自动生成, -Git 获取服务器中 `refs/heads/` 下面的所有引用,并将它写入到本地的 `refs/remotes/origin/` 中。 +默认情况下,引用规范由 `git remote add origin` 命令自动生成,Git 获取服务器中 `refs/heads/` 下面的所有引用,并将它写入到本地的 `refs/remotes/origin/` 中。 所以,如果服务器上有一个 `master` 分支,你可以在本地通过下面任意一种方式来访问该分支上的提交记录: [source,console] @@ -37,8 +33,7 @@ $ git log refs/remotes/origin/master 上面的三个命令作用相同,因为 Git 会把它们都扩展成 `refs/remotes/origin/master`。 -如果想让 Git 每次只拉取远程的 `master` 分支,而不是所有分支, -可以把(引用规范的)获取那一行修改为只引用该分支: +如果想让 Git 每次只拉取远程的 `master` 分支,而不是所有分支,可以把(引用规范的)获取那一行修改为只引用该分支: [source] ---- @@ -88,8 +83,7 @@ fetch = +refs/heads/qa*:refs/remotes/origin/qa* ---- 更棒的是,我们可以使用命名空间(或目录)来实现相同的目标,并且更具结构性。 -假设你有一个 QA 团队,他们推送了一系列分支,同时你只想要获取 `master` 和 -QA 团队的所有分支而不关心其他任何分支,那么可以使用如下配置: +假设你有一个 QA 团队,他们推送了一系列分支,同时你只想要获取 `master` 和 QA 团队的所有分支而不关心其他任何分支,那么可以使用如下配置: [source,ini] ---- diff --git a/book/10-git-internals/sections/transfer-protocols.asc b/book/10-git-internals/sections/transfer-protocols.asc index 68a9d90f..8eefb5a1 100644 --- a/book/10-git-internals/sections/transfer-protocols.asc +++ b/book/10-git-internals/sections/transfer-protocols.asc @@ -110,8 +110,7 @@ P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack (4k of binary data) ---- -现在你有这个包文件的索引,你可以查看你要的对象是否在里面—— -因为索引文件列出了这个包文件所包含的所有对象的 SHA-1 值,和该对象存在于包文件中的偏移量。 +现在你有这个包文件的索引,你可以查看你要的对象是否在里面——因为索引文件列出了这个包文件所包含的所有对象的 SHA-1 值,和该对象存在于包文件中的偏移量。 你的对象就在这里,接下来就是获取整个包文件: [source] @@ -154,9 +153,11 @@ $ ssh -x git@server "git-receive-pack 'simplegit-progit.git'" `git-receive-pack` 命令会立即为它所拥有的每一个引用发送一行响应——在这个例子中,就只有 `master` 分支和它的 SHA-1 值。 第一行响应中也包含了一个服务端能力的列表(这里是 `report-status`、`delete-refs` 和一些其它的,包括客户端的识别码)。 -每一行以一个四位的十六进制值开始,用于指明本行的长度。 -你看到第一行以 00a5 开始,这在十六进制中表示 165,意味着第一行有 165 字节。 -下一行是 0000,表示服务端已完成了发送引用列表过程。 +数据是以块(Chunk)为单位传输的。 +每一块以一个四位的十六进制值开始,用于指明本数据块的长度。 +数据块通常包含一行数据及一个末尾的换行符。 +你看到第一个数据块以 00a5 开始,这在十六进制中表示 165,意味着第一个数据块有 165 字节。 +下一块是 0000,表示服务端已完成了发送引用列表过程。 现在它知道了服务端的状态,你的 `send-pack` 进程会判断哪些提交记录是它所拥有但服务端没有的。 `send-pack` 会告知 `receive-pack` 这次推送将会更新的各个引用。 @@ -171,6 +172,7 @@ $ ssh -x git@server "git-receive-pack 'simplegit-progit.git'" 0000 ---- +对于您要更新的每一个引用,Git 都会发送一行数据,其中包含该行的长度、旧的 SHA-1 哈希值、新的 SHA-1 哈希值,以及正在被更新的引用名称。 第一行也包括了客户端的能力。 这里的全为 '0' 的 SHA-1 值表示之前没有过这个引用——因为你正要添加新的 experiment 引用。 删除引用时,将会看到相反的情况:右边的 SHA-1 值全为 '0'。 diff --git a/book/A-git-in-other-environments/sections/guis.asc b/book/A-git-in-other-environments/sections/guis.asc index dbbe602b..2c083191 100644 --- a/book/A-git-in-other-environments/sections/guis.asc +++ b/book/A-git-in-other-environments/sections/guis.asc @@ -96,7 +96,7 @@ image::images/github_win.png[GitHub Windows 客户端。] ===== 安装 -GitHub 的 Windows 客户端可以从 https://windows.github.com[] 下载,macOS 客户端可以从 https://mac.github.com[] 下载。 +GitHub 的 Windows 和 Mac 客户端可以从 https://github.com/apps/desktop[^] 下载。 第一次打开软件时,它会引导你进行一系列的首次使用设置,例如设置你的姓名和电子邮件,它还会智能地帮你调整一些常用的默认设置,例如凭证缓存和 CRLF 的处理方式。 它们都是“绿色软件”——如果软件打开发现有更新,下载和安装升级包都是在后台完成的。 @@ -147,5 +147,5 @@ push,fetch,merge,和 rebase 在 Git 内部是一连串独立的操作, 而 ==== 其它图形界面 除此之外,还有许许多多其它的图形化 Git 客户端,其中既有单一功能的定制工具,也有试图提供 Git 所有功能的复杂应用。 -Git 的官方网站整理了一份时下最流行的客户端的清单 https://git-scm.com/downloads/guis[]。 -在 Git 的维基站点还可以看到一份更全的清单 https://git.wiki.kernel.org/index.php/Interfaces,_frontends,_and_tools#Graphical_Interfaces[]。 +Git 的官方网站整理了一份时下最流行的客户端的清单 https://git-scm.com/downloads/guis[^]。 +在 Git 的维基站点还可以看到一份更全的清单 https://archive.kernel.org/oldwiki/git.wiki.kernel.org/index.php/Interfaces,_frontends,_and_tools.html#Graphical_Interfaces[^]。 diff --git a/book/A-git-in-other-environments/sections/jetbrainsides.asc b/book/A-git-in-other-environments/sections/jetbrainsides.asc index 8cff3005..a611f3ba 100644 --- a/book/A-git-in-other-environments/sections/jetbrainsides.asc +++ b/book/A-git-in-other-environments/sections/jetbrainsides.asc @@ -8,4 +8,4 @@ JetBrains IDEs(比如 IntelliJ IDEA,PyCharm,WebStorm,PhpStorm,RubyMine image::images/jb.png[JetBrains IDEs 中的版本控制工具窗口。] 该集成插件依赖于 Git 的命令行客户端,所以需要先安装一个 Git 客户端。 -官方文档请访问 https://www.jetbrains.com/help/idea/using-git-integration.html。 +官方文档请访问 https://www.jetbrains.com/help/idea/using-git-integration.html[^]。 diff --git a/book/A-git-in-other-environments/sections/powershell.asc b/book/A-git-in-other-environments/sections/powershell.asc index d8650538..33f0ac82 100644 --- a/book/A-git-in-other-environments/sections/powershell.asc +++ b/book/A-git-in-other-environments/sections/powershell.asc @@ -5,7 +5,7 @@ (((posh-git))) Windows 中早期的命令行终端 `cmd.exe` 无法自定义 Git 使用体验,但是如果你正在使用 Powershell,那么你就十分幸运了。 这种方法同样适用于 Linux 或 macOS 上运行的 PowerShell Core。 -一个名为 posh-git (https://github.com/dahlbyk/posh-git[]) 的扩展包提供了强大的 tab 补全功能,并针对提示符进行了增强,以帮助你聚焦于你的仓库状态。 +一个名为 posh-git (https://github.com/dahlbyk/posh-git[^]) 的扩展包提供了强大的 tab 补全功能,并针对提示符进行了增强,以帮助你聚焦于你的仓库状态。 它看起来像: .附带了 posh-git 扩展包的 Powershell。 @@ -21,9 +21,9 @@ image::images/posh-git.png[附带了 posh-git 扩展包的 Powershell] 如果你是管理员,想要为本机上的所有用户设置它,请使用 `-Scope LocalMachine`。 如果你是没有管理权限的普通用户,可使用 `-Scope CurrentUser` 来只为自己设置它。 -有关 PowerShell Scopes 的更多详情: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes[] +有关 PowerShell Scopes 的更多详情: https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_scopes[^] -有关 PowerShell ExecutionPolicy 的更多详情: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy[] +有关 PowerShell ExecutionPolicy 的更多详情: https://learn.microsoft.com/zh-cn/powershell/module/microsoft.powershell.security/set-executionpolicy[^] 对于所有用户使用以下命令来设置 `ExecutionPolicy` 为 `RemoteSigned`: @@ -36,7 +36,7 @@ image::images/posh-git.png[附带了 posh-git 扩展包的 Powershell] 如果你有 PowerShell 5 以上或安装了 PackageManagement 的 PowerShell 4,那么可以用包管理器来安装 posh-git。 -有关 PowerShell Gallery 的更多详情: https://docs.microsoft.com/en-us/powershell/scripting/gallery/overview[] +有关 PowerShell Gallery 的更多详情: https://learn.microsoft.com/zh-cn/powershell/scripting/gallery/overview[^] [source,powershell] ----- @@ -71,7 +71,7 @@ image::images/posh-git.png[附带了 posh-git 扩展包的 Powershell] ===== 从源码安装 -只需从 https://github.com/dahlbyk/posh-git[] 下载一份 posh-git 的发行版并解压即可。 +只需从 https://github.com/dahlbyk/posh-git/releases[^] 下载一份 posh-git 的发行版并解压即可。 接着使用 `posh-git.psd1` 文件的完整路径导入此模块: [source,powershell] @@ -82,5 +82,5 @@ image::images/posh-git.png[附带了 posh-git 扩展包的 Powershell] 它将会向你的 `profile.ps1` 文件添加适当的内容,posh-git 将会在下次打开 PowerShell 时启用。 -命令提示符显示的 Git 状态信息的解释见: https://github.com/dahlbyk/posh-git/blob/master/README.md#git-status-summary-information[] -如何定制 Posh-Git 提示符的详情见: https://github.com/dahlbyk/posh-git/blob/master/README.md#customization-variables[]。 +命令提示符显示的 Git 状态信息的解释见: https://github.com/dahlbyk/posh-git/blob/master/README.md#git-status-summary-information[^] +如何定制 Posh-Git 提示符的详情见: https://github.com/dahlbyk/posh-git/blob/master/README.md#customization-variables[^]。 diff --git a/book/A-git-in-other-environments/sections/sublimetext.asc b/book/A-git-in-other-environments/sections/sublimetext.asc index 7013ed4c..14b91817 100644 --- a/book/A-git-in-other-environments/sections/sublimetext.asc +++ b/book/A-git-in-other-environments/sections/sublimetext.asc @@ -11,6 +11,6 @@ * 对一个文件的所有改动都会通过行号槽上的记号显示出来。 * 你能够在 Sublime Text 内使用 Sublime Merge 这个 Git 客户端的部分功能。 要求安装 Sublime Merge: - https://www.sublimemerge.com/。 + https://www.sublimemerge.com/[^]。 -Sublime Text 的官方文档请访问:https://www.sublimetext.com/docs/3/git_integration.html。 +Sublime Text 的官方文档请访问:https://www.sublimetext.com/docs/git_integration.html[^]。 diff --git a/book/A-git-in-other-environments/sections/visualstudio.asc b/book/A-git-in-other-environments/sections/visualstudio.asc index 39c47012..159bcccd 100644 --- a/book/A-git-in-other-environments/sections/visualstudio.asc +++ b/book/A-git-in-other-environments/sections/visualstudio.asc @@ -15,4 +15,4 @@ * 查看差异。 * ... 等等。 -阅读 https://docs.microsoft.com/visualstudio/version-control[official documentation^] 以了解更多内容。 +阅读 https://learn.microsoft.com/zh-cn/visualstudio/version-control/git-with-visual-studio[official documentation^] 以了解更多内容。 diff --git a/book/A-git-in-other-environments/sections/visualstudiocode.asc b/book/A-git-in-other-environments/sections/visualstudiocode.asc index 4367b56e..dfb2ee67 100644 --- a/book/A-git-in-other-environments/sections/visualstudiocode.asc +++ b/book/A-git-in-other-environments/sections/visualstudiocode.asc @@ -17,6 +17,6 @@ Visual Studio Code 自带对 Git 的支持。 ** 解决合并冲突。 ** 查看比较。 * 配合一个扩展,你也能够处理 GitHub 的拉取请求: - https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github。 + https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github[^]。 -官方文档请访问: https://code.visualstudio.com/Docs/editor/versioncontrol。 +官方文档请访问: https://code.visualstudio.com/docs/sourcecontrol/overview[^]。 diff --git a/book/A-git-in-other-environments/sections/zsh.asc b/book/A-git-in-other-environments/sections/zsh.asc index 9ae7165e..27723885 100644 --- a/book/A-git-in-other-environments/sections/zsh.asc +++ b/book/A-git-in-other-environments/sections/zsh.asc @@ -40,13 +40,13 @@ zstyle ':vcs_info:git:*' formats '%b' .自定义 `zsh` 提示符. image::images/zsh-prompt.png[自定义 `zsh` 提示符.] -关于 `vcs_info` 的更多信息,可参见 `zshcontrib(1)` 手册页面中对应的文档,或访问 http://zsh.sourceforge.net/Doc/Release/User-Contributions.html#Version-Control-Information[] 在线浏览。 +关于 `vcs_info` 的更多信息,可参见 `zshcontrib(1)` 手册页面中对应的文档,或访问 https://zsh.sourceforge.io/Doc/Release/User-Contributions.html#Version-Control-Information[^] 在线浏览。 -比起 `vcs_info` 而言,你可能更偏好提供了 Git 的命令提示符定制脚本 `git-prompt.sh`;更多信息见 https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh[]。 +比起 `vcs_info` 而言,你可能更偏好提供了 Git 的命令提示符定制脚本 `git-prompt.sh`;更多信息见 https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh[^]。 `git-prompt.sh` 同时兼容 Bash 和 Zsh。 Zsh 本身已足够强大,但还有一些专门为它打造的完整框架,使它更加完善。 -其中之一名为 "oh-my-zsh",你可以在 https://github.com/robbyrussell/oh-my-zsh[] 找到它。 +其中之一名为 "oh-my-zsh",你可以在 https://github.com/ohmyzsh/ohmyzsh[^] 找到它。 oh-my-zsh 的扩展系统包含强大的 Git Tab 补全功能,且许多提示符 "主题" 可以展示版本控制数据。 <> 只是可以其中一个可以通过该系统实现的例子。 diff --git a/book/B-embedding-git/sections/dulwich.asc b/book/B-embedding-git/sections/dulwich.asc index ca0754dd..6ba81947 100644 --- a/book/B-embedding-git/sections/dulwich.asc +++ b/book/B-embedding-git/sections/dulwich.asc @@ -2,7 +2,7 @@ (((Dulwich)))(((Python))) 这还有一个纯-Python 的 Git 实现——Dulwich。 -该项目托管在 https://www.dulwich.io/[]。 +该项目托管在 https://www.dulwich.io/[^]。 它旨在在提供一个与 Git 存储库(本地和远程)的接口,该接口不直接调用 git,而是使用纯 Python。 它有一个可选的 C 扩展,可以显著提高性能。 @@ -40,4 +40,4 @@ porcelain.log('.', max_entries=1) ==== 拓展阅读 -在官方网站 https://www.dulwich.io[] 上可以找到 API 文档、教程和许多关于如何使用 Dulwich 完成特定任务的示例。 +在官方网站 https://www.dulwich.io[^] 上可以找到 API 文档、教程和许多关于如何使用 Dulwich 完成特定任务的示例。 diff --git a/book/B-embedding-git/sections/go-git.asc b/book/B-embedding-git/sections/go-git.asc index 69fa82df..a7f9b9ff 100644 --- a/book/B-embedding-git/sections/go-git.asc +++ b/book/B-embedding-git/sections/go-git.asc @@ -5,23 +5,22 @@ 这个库的实现没有任何原生依赖,因此不易出现手动管理内存的错误。 它对于标准 Golang 性能分析工具(如 CPU、内存分析器、竞争检测器等)也是透明的。 -go-git 专注于可扩展性、兼容性并支持大多数管道 API,记录在 https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md[]. +go-git 专注于可扩展性、兼容性并支持大多数管道 API,记录在 https://github.com/go-git/go-git/blob/master/COMPATIBILITY.md[^]. 以下是使用 Go API 的基本示例: [source, go] ----- -import "gopkg.in/src-d/go-git.v4" +import "github.com/go-git/go-git/v5" r, err := git.PlainClone("/tmp/foo", false, &git.CloneOptions{ - URL: "https://github.com/src-d/go-git", + URL: "https://github.com/go-git/go-git", Progress: os.Stdout, }) ----- 你只要拥有一个 `Repository` 实例,就可以访问相应仓库信息并对其进行改变: - [source, go] ----- // 获取 HEAD 指向的分支 @@ -39,7 +38,6 @@ for _, c := range history { } ----- - ==== 高级功能 go-git 几乎没有值得注意的高级功能,其中之一是可插拔存储系统,类似于 Libgit2 后端。 @@ -48,17 +46,17 @@ go-git 几乎没有值得注意的高级功能,其中之一是可插拔存储 [source, go] ----- r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ - URL: "https://github.com/src-d/go-git", + URL: "https://github.com/go-git/go-git", }) ----- 可插拔存储提供了许多有趣的选项。 -例如,https://github.com/go-git/go-git/tree/master/_examples/storage[] 允许你在 Aerospike 数据库中存储引用、对象和配置。 +例如,https://github.com/go-git/go-git/tree/master/_examples/storage[^] 允许你在 Aerospike 数据库中存储引用、对象和配置。 另一个特性是灵活的文件系统抽象。 -使用 https://godoc.org/github.com/src-d/go-billy#Filesystem[] 可以很容易以不同的方式存储所有文件,即通过将所有文件打包到磁盘上的单个归档文件或保存它们都在内存中。 +使用 https://pkg.go.dev/github.com/go-git/go-billy/v5?tab=doc#Filesystem[^] 可以很容易以不同的方式存储所有文件,即通过将所有文件打包到磁盘上的单个归档文件或保存它们都在内存中。 -另一个高级用例包括一个可微调的 HTTP 客户端,例如 https://github.com/go-git/go-git/blob/master/_examples/custom_http/main.go[] 中的案例。 +另一个高级用例包括一个可微调的 HTTP 客户端,例如 https://github.com/go-git/go-git/blob/master/_examples/custom_http/main.go[^] 中的案例。 [source, go] ----- @@ -83,4 +81,4 @@ r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{URL: url}) ==== 延伸阅读 对 go-git 功能的全面介绍超出了本书的范围。 -如果您想了解有关 go-git 的更多信息,请参阅 https://godoc.org/gopkg.in/src-d/go-git.v4[] 上的 API 文档, 以及 https://github.com/go-git/go-git/tree/master/_examples[] 上的系列使用示例。 \ No newline at end of file +如果您想了解有关 go-git 的更多信息,请参阅 https://pkg.go.dev/github.com/go-git/go-git/v5[^] 上的 API 文档, 以及 https://github.com/go-git/go-git/tree/master/_examples[^] 上的系列使用示例。 \ No newline at end of file diff --git a/book/B-embedding-git/sections/jgit.asc b/book/B-embedding-git/sections/jgit.asc index cbd3ed4a..97a4aed1 100644 --- a/book/B-embedding-git/sections/jgit.asc +++ b/book/B-embedding-git/sections/jgit.asc @@ -3,7 +3,7 @@ (((jgit)))(((Java))) 如果你想在一个 Java 程序中使用 Git ,有一个功能齐全的 Git 库,那就是 JGit 。 JGit 是一个用 Java 写成的功能相对健全的 Git 的实现,它在 Java 社区中被广泛使用。 -JGit 项目由 Eclipse 维护,它的主页在 https://www.eclipse.org/jgit[] 。 +JGit 项目由 Eclipse 维护,它的主页在 https://projects.eclipse.org/projects/technology.jgit[^]。 ==== 起步 @@ -19,10 +19,10 @@ JGit 项目由 Eclipse 维护,它的主页在 https://www.eclipse.org/jgit[] ---- -在你读到这段文字时 `version` 很可能已经更新了,所以请浏览 https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit[] 以获取最新的仓库信息。 +在你读到这段文字时 `version` 很可能已经更新了,所以请浏览 https://mvnrepository.com/artifact/org.eclipse.jgit/org.eclipse.jgit[^] 以获取最新的仓库信息。 当这一步完成之后, Maven 就会自动获取并使用你所需要的 JGit 库。 -如果你想自己管理二进制的依赖包,那么你可以从 https://www.eclipse.org/jgit/download[] 获得预构建的 JGit 二进制文件。 +如果你想自己管理二进制的依赖包,那么你可以从 https://projects.eclipse.org/projects/technology.jgit/downloads[^] 获得预构建的 JGit 二进制文件。 你可以像下面这样执行一个命令来将它们构建进你的项目。 [source,console] @@ -155,6 +155,6 @@ for (Ref ref : remoteRefs) { 这只是 JGit 的全部能力的冰山一角。 如果你对这有兴趣并且想深入学习,在下面可以找到一些信息和灵感。 -* JGit API 在线官方文档: https://www.eclipse.org/jgit/documentation[] 。 +* JGit API 在线官方文档: https://help.eclipse.org/latest/topic/org.eclipse.egit.doc/help/JGit/User_Guide/User-Guide.html[^]。 这是基本的 Javadoc ,所以你也可以在你最喜欢的 JVM IDE 上将它们安装它们到本地。 -* JGit Cookbook : https://github.com/centic9/jgit-cookbook[] 拥有许多如何利用 JGit 实现特定任务的例子。 +* JGit Cookbook : https://github.com/centic9/jgit-cookbook[^] 拥有许多如何利用 JGit 实现特定任务的例子。 diff --git a/book/B-embedding-git/sections/libgit2.asc b/book/B-embedding-git/sections/libgit2.asc index 7cfc76d0..833c8d48 100644 --- a/book/B-embedding-git/sections/libgit2.asc +++ b/book/B-embedding-git/sections/libgit2.asc @@ -3,7 +3,7 @@ (((libgit2)))(((C))) 另外一种可以供你使用的是 Libgit2。 Libgit2 是一个 Git 的非依赖性的工具,它致力于为其他程序使用 Git 提供更好的 API。 -你可以在 https://libgit2.org[] 找到它。 +你可以在 https://libgit2.org[^] 找到它。 首先,让我们来看一下 C API 长啥样。 这是一个旋风式旅行。 @@ -54,7 +54,7 @@ git_repository_free(repo); (((Ruby))) 最后一点意味着你应该不会在使用 Libgit2 时编写 C 语言程序。 但幸运的是,有许多可用的各种语言的绑定,能让你在特定的语言和环境中更加容易的操作 Git 版本库。 -我们来看一下下面这个用 Libgit2 的 Ruby 绑定写成的例子,它叫 Rugged,你可以在 https://github.com/libgit2/rugged[] 找到它。 +我们来看一下下面这个用 Libgit2 的 Ruby 绑定写成的例子,它叫 Rugged,你可以在 https://github.com/libgit2/rugged[^] 找到它。 [source,ruby] ---- @@ -108,7 +108,6 @@ commit = repo.lookup(commit_id) # <8> Ruby 的代码很好很简洁,另一方面因为 Libgit2 做了大量工作,所以代码运行起来其实速度也不赖。 如果你不是一个 Ruby 程序员,我们在 <<_libgit2_bindings>> 有提到其它的一些绑定。 - ==== 高级功能 Libgit2 有几个超过核心 Git 的能力。 @@ -116,7 +115,7 @@ Libgit2 有几个超过核心 Git 的能力。 Libgit2 允许为自定义后端指定配置、引用的存储以及对象数据库, 我们来看一下它究竟是怎么工作的。 -下面的例子借用自 Libgit2 团队提供的后端样本集 (可以在 https://github.com/libgit2/libgit2-backends[] 上找到)。 +下面的例子借用自 Libgit2 团队提供的后端样本集 (可以在 https://github.com/libgit2/libgit2-backends[^] 上找到)。 一个对象数据库的自定义后端是这样建立的: [source,c] @@ -184,14 +183,13 @@ int git_odb_backend_mine(git_odb_backend **backend_out, /*…*/) Libgit2 有很多种语言的绑定。 在这篇文章中,我们展现了一个使用了几个更加完整的绑定包的小例子,这些库存在于许多种语言中,包括 C++、Go、Node.js、Erlang 以及 JVM ,它们的成熟度各不相同。 -官方的绑定集合可以通过浏览这个版本库得到: https://github.com/libgit2[] 。 +官方的绑定集合可以通过浏览这个版本库得到: https://github.com/libgit2[^] 。 我们写的代码将返回当前 HEAD 指向的提交的提交信息(就像 `git log -1` 那样)。 - ===== LibGit2Sharp (((.NET)))(((C#)))(((Mono))) -如果你在编写一个 .NET 或者 Mono 应用,那么 LibGit2Sharp (https://github.com/libgit2/libgit2sharp[]) 就是你所需要的。 +如果你在编写一个 .NET 或者 Mono 应用,那么 LibGit2Sharp (https://github.com/libgit2/libgit2sharp[^]) 就是你所需要的。 这个绑定是用 C# 写成的,并且已经采取许多措施来用令人感到自然的 CLR API 包装原始的 Libgit2 的调用。 我们的例子看起来就像这样: @@ -206,7 +204,7 @@ new Repository(@"C:\path\to\repo").Head.Tip.Message; (((Apple)))(((Objective-C)))(((Cocoa))) 如果你的应用运行在一个 Apple 平台上,你很有可能使用 Objective-C 作为实现语言。 -Objective-Git (https://github.com/libgit2/objective-git[]) 是这个环境下的 Libgit2 绑定。 +Objective-Git (https://github.com/libgit2/objective-git[^]) 是这个环境下的 Libgit2 绑定。 一个例子看起来类似这样: [source,objc] @@ -218,11 +216,10 @@ NSString *msg = [[[epo headReferenceWithError:NULL] resolvedTarget] message]; Objective-git 与 Swift 完美兼容,所以你把 Objective-C 落在一边的时候不用恐惧。 - ===== pygit2 (((Python))) -Python 的 Libgit2 绑定叫做 Pygit2 ,你可以在 https://www.pygit2.org/[] 找到它。 +Python 的 Libgit2 绑定叫做 Pygit2 ,你可以在 https://www.pygit2.org[^] 找到它。 我们的示例程序: [source,python] @@ -233,9 +230,8 @@ pygit2.Repository("/path/to/repo") # 打开代码仓库 .message # 读取提交信息 ---- - ==== 扩展阅读 当然,完全阐述 Libgit2 的能力已超出本书范围。 -如果你想了解更多关于 Libgit2 的信息,可以浏览它的 API 文档: https://libgit2.github.com/libgit2[], 以及一系列的指南: https://libgit2.github.com/docs[]. +如果你想了解更多关于 Libgit2 的信息,可以浏览它的 API 文档: https://libgit2.org/docs/reference[^], 以及一系列的指南: https://libgit2.org/docs[^]. 对于其它的绑定,检查附带的 README 和测试文件,那里通常有简易教程,以及指向拓展阅读的链接。