Pro Git

Size: px
Start display at page:

Download "Pro Git"

Transcription

1

2 Pro Git

3 Table of Contents Pro Git Scott Chacon 序 Ben Straub 序 献 辞 起 步 关 于 版 本 控 制 Git 简 史 Git 基 础 命 令 行 安 装 Git 初 次 运 行 Git 前 的 配 置 获 取 帮 助 总 结 Git 基 础 获 取 Git 仓 库 记 录 每 次 更 新 到 仓 库 查 看 提 交 历 史 撤 消 操 作 远 程 仓 库 的 使 用 打 标 签 Git 别 名 总 结 Git 分 支 分 支 简 介 分 支 的 新 建 与 合 并 分 支 管 理 分 支 开 发 工 作 流 远 程 分 支 变 基 总 结 服 务 器 上 的 Git 协 议 在 服 务 器 上 搭 建 Git 生 成 SSH 公 钥 配 置 服 务 器 Git 守 护 进 程 Smart HTTP GitWeb GitLab 第 三 方 托 管 的 选 择 总 结 分 布 式 Git 分 布 式 工 作 流 程 向 一 个 项 目 贡 献 维 护 项 目

4 总 结 GitHub 账 户 的 创 建 和 配 置 对 项 目 做 出 贡 献 维 护 项 目 管 理 组 织 脚 本 GitHub 总 结 Git 工 具 选 择 修 订 版 本 交 互 式 暂 存 储 藏 与 清 理 签 署 工 作 搜 索 重 写 历 史 重 置 揭 密 高 级 合 并 Rerere 使 用 Git 调 试 子 模 块 打 包 替 换 凭 证 存 储 总 结 自 定 义 Git 配 置 Git Git 属 性 Git 钩 子 使 用 强 制 策 略 的 一 个 例 子 总 结 Git 与 其 他 系 统 作 为 客 户 端 的 Git 迁 移 到 Git 总 结 Git 内 部 原 理 底 层 命 令 和 高 层 命 令 Git 对 象 Git 引 用 包 文 件 引 用 规 格 传 输 协 议 维 护 与 数 据 恢 复 环 境 变 量 总 结 Appendix A: 其 它 环 境 中 的 Git 图 形 界 面 Visual Studio 中 的 Git Eclipse 中 的 Git

5 Bash 中 的 Git Zsh 中 的 Git Powershell 中 的 Git 总 结 Appendix B: 将 Git 嵌 入 你 的 应 用 命 令 行 Git 方 式 Libgit JGit Appendix C: Git 命 令 设 置 与 配 置 获 取 与 创 建 项 目 快 照 基 础 分 支 与 合 并 项 目 分 享 与 更 新 检 查 与 比 较 调 试 补 丁 邮 件 外 部 系 统 管 理 底 层 命 令

6 Pro Git

7 Scott Chacon 序 欢 迎 来 到 Pro Git 第 二 版 第 一 版 出 版 到 现 在 已 经 过 去 了 四 年 到 今 天,Git 虽 然 出 现 了 许 多 改 变, 但 是 还 有 很 多 重 要 的 事 情 一 如 昨 日 因 为 Git 核 心 团 队 对 保 持 向 后 兼 容 性 异 常 固 执, 所 以 直 到 今 天 大 多 数 核 心 命 令 与 概 念 依 然 有 效, 但 是 围 绕 Git 的 社 区 还 是 有 一 些 重 大 的 增 加 与 改 变 本 书 的 第 二 版 就 是 为 了 更 新 书 籍 并 讲 解 那 些 改 动 以 使 其 对 新 用 户 更 有 帮 助 当 我 写 第 一 版 时,Git 对 于 超 级 黑 客 来 说 还 是 一 个 相 对 难 用, 只 能 勉 强 接 受 的 工 具 它 开 始 在 特 定 的 社 区 中 快 速 发 展, 但 是 还 没 有 达 到 像 今 天 一 样 无 处 不 在 的 地 步 自 那 时 起, 几 乎 每 一 个 开 源 社 区 都 采 用 了 它 Git 在 Windows 上 取 得 了 难 以 置 信 的 进 步, 包 括 所 有 平 台 的 图 形 用 户 界 面 对 它 的 支 持 IDE 的 支 持, 以 及 商 业 使 用 的 爆 炸 式 发 展 四 年 前 的 Pro Git 对 此 一 无 所 知 新 版 本 的 主 要 目 标 之 一 就 是 涉 及 Git 社 区 中 那 些 所 有 新 的 前 沿 领 域 使 用 Git 的 开 源 社 区 也 呈 现 出 爆 炸 式 的 发 展 大 概 在 五 年 前 吧, 我 坐 下 来 写 这 本 书 时 ( 写 完 第 一 个 版 本 花 了 我 不 少 时 间 ), 我 开 始 在 一 个 知 名 度 极 小 的 开 发 Git 托 管 网 站 的 公 司 工 作, 这 家 公 司 就 是 GitHub 本 书 出 版 时 大 概 有 几 千 人 在 使 用 GitHub 网 站, 而 为 其 工 作 的 只 有 我 们 四 个 人 在 我 写 这 篇 介 绍 时,GitHub 宣 布 我 们 托 管 了 1000 万 个 项 目 拥 有 大 概 500 万 注 册 开 发 者 账 户 与 大 概 230 名 员 工 爱 它 也 好, 恨 它 也 罢, 当 我 坐 下 来 写 第 一 版 时,GitHub 以 一 种 意 想 不 到 的 方 式 猛 烈 地 改 变 了 一 大 批 开 源 社 区 我 在 Pro Git 的 原 始 版 本 中 写 了 一 节 我 并 不 是 很 满 意 的 内 容, 是 作 为 和 提 供 Git 托 管 服 务 相 关 的 例 子 的 GitHub 我 在 书 里 写 的 东 西 本 质 上 都 是 和 社 区 有 关 的, 但 是 又 不 得 不 讨 论 到 我 的 公 司, 这 点 我 不 喜 欢 同 时 我 还 不 喜 欢 那 个 兴 趣 的 冲 突,GitHub 在 Git 社 区 中 的 重 要 性 是 无 法 避 免 的 我 已 经 决 定 将 本 书 的 那 部 分 转 变 为 深 度 介 绍 GitHub 是 什 么 以 及 如 何 高 效 地 使 用 它, 而 不 再 是 作 为 一 个 Git 托 管 的 例 子 如 果 你 正 学 习 如 何 使 用 Git, 那 么 了 解 如 何 使 用 GitHub 将 会 帮 助 你 加 入 到 一 个 巨 大 的 社 区 中 不 论 你 决 定 为 自 己 的 代 码 使 用 哪 一 个 Git 托 管 服 务, 这 都 很 有 价 值 自 从 上 次 出 版 以 来 另 一 个 重 大 变 革 是 Git 网 络 传 输 HTTP 协 议 的 开 发 与 崛 起 书 中 的 大 多 数 例 子 都 已 经 从 SSH 切 换 到 HTTP, 因 为 它 更 简 单 在 过 去 这 几 年 看 到 Git 从 一 个 相 对 无 名 的 版 本 管 理 系 统 成 长 为 商 业 与 开 源 版 本 管 理 的 事 实 标 准 是 令 人 吃 惊 的 我 很 高 兴 Pro Git 做 得 很 好 并 已 经 成 为 市 场 上 几 本 既 成 功 又 完 全 开 源 的 技 术 书 籍 之 一 我 希 望 你 能 享 受 这 个 升 级 版 的 Pro Git

8 Ben Straub 序 本 书 的 第 一 版 就 是 将 我 与 Git 结 下 不 解 之 缘 的 原 因 书 中 采 用 的 是 我 引 进 的 做 软 件 的 风 格, 这 种 风 格 比 我 之 前 看 到 的 任 何 事 情 都 要 自 然 那 时 我 已 经 做 了 好 几 年 开 发 者 了, 但 是 这 本 书 将 我 指 引 到 一 条 更 加 精 彩 的 道 路 上 几 年 之 后 的 现 在, 我 是 Git 的 一 个 主 要 实 现 的 贡 献 者, 我 在 最 大 的 Git 托 管 公 司 工 作, 我 已 经 环 游 世 界 教 人 们 使 用 Git 当 Scott 问 我 是 否 有 兴 趣 在 第 二 版 上 工 作 时, 我 甚 至 连 想 都 没 想 就 答 应 了 能 在 这 本 书 上 工 作 是 一 份 巨 大 的 快 乐 与 荣 耀 我 希 望 它 能 像 帮 助 我 一 样 帮 助 你

9 献 辞 致 我 的 妻 子,Becky, 没 有 她 的 话 这 段 冒 险 不 会 开 始 Ben 谨 以 此 书 献 给 我 的 家 人 给 这 些 年 一 直 支 持 着 我 的 妻 子 Jessica 和 女 儿 Josephine, 还 有 那 些 在 我 风 烛 残 年 之 时 还 能 支 持 我 的 人 Scott

10 起 步 本 章 关 于 开 始 学 习 Git 我 们 从 介 绍 有 关 版 本 控 制 工 具 的 一 些 背 景 知 识 开 始, 然 后 讲 解 如 何 在 你 的 系 统 运 行 Git, 最 后 是 关 于 如 何 设 置 Git 开 始 你 的 工 作 通 过 本 章 的 学 习, 你 应 该 了 解 为 什 么 Git 这 么 流 行, 为 什 么 你 应 该 使 用 Git 以 及 你 应 该 如 何 设 置 以 便 使 用 Git 关 于 版 本 控 制 什 么 是 版 本 控 制? 我 为 什 么 要 关 心 它 呢? 版 本 控 制 是 一 种 记 录 一 个 或 若 干 文 件 内 容 变 化, 以 便 将 来 查 阅 特 定 版 本 修 订 情 况 的 系 统 在 本 书 所 展 示 的 例 子 中, 我 们 对 保 存 着 软 件 源 代 码 的 文 件 作 版 本 控 制, 但 实 际 上, 你 可 以 对 任 何 类 型 的 文 件 进 行 版 本 控 制 如 果 你 是 位 图 形 或 网 页 设 计 师, 可 能 会 需 要 保 存 某 一 幅 图 片 或 页 面 布 局 文 件 的 所 有 修 订 版 本 ( 这 或 许 是 你 非 常 渴 望 拥 有 的 功 能 ), 采 用 版 本 控 制 系 统 (VCS) 是 个 明 智 的 选 择 有 了 它 你 就 可 以 将 某 个 文 件 回 溯 到 之 前 的 状 态, 甚 至 将 整 个 项 目 都 回 退 到 过 去 某 个 时 间 点 的 状 态, 你 可 以 比 较 文 件 的 变 化 细 节, 查 出 最 后 是 谁 修 改 了 哪 个 地 方, 从 而 找 出 导 致 怪 异 问 题 出 现 的 原 因, 又 是 谁 在 何 时 报 告 了 某 个 功 能 缺 陷 等 等 使 用 版 本 控 制 系 统 通 常 还 意 味 着, 就 算 你 乱 来 一 气 把 整 个 项 目 中 的 文 件 改 的 改 删 的 删, 你 也 照 样 可 以 轻 松 恢 复 到 原 先 的 样 子 但 额 外 增 加 的 工 作 量 却 微 乎 其 微 本 地 版 本 控 制 系 统 许 多 人 习 惯 用 复 制 整 个 项 目 目 录 的 方 式 来 保 存 不 同 的 版 本, 或 许 还 会 改 名 加 上 备 份 时 间 以 示 区 别 这 么 做 唯 一 的 好 处 就 是 简 单, 但 是 特 别 容 易 犯 错 有 时 候 会 混 淆 所 在 的 工 作 目 录, 一 不 小 心 会 写 错 文 件 或 者 覆 盖 意 想 外 的 文 件 为 了 解 决 这 个 问 题, 人 们 很 久 以 前 就 开 发 了 许 多 种 本 地 版 本 控 制 系 统, 大 多 都 是 采 用 某 种 简 单 的 数 据 库 来 记 录 文 件 的 历 次 更 新 差 异

11 Figure 1. 本 地 版 本 控 制. 其 中 最 流 行 的 一 种 叫 做 RCS, 现 今 许 多 计 算 机 系 统 上 都 还 看 得 到 它 的 踪 影 甚 至 在 流 行 的 Mac OS X 系 统 上 安 装 了 开 发 者 工 具 包 之 后, 也 可 以 使 用 rcs 命 令 它 的 工 作 原 理 是 在 硬 盘 上 保 存 补 丁 集 ( 补 丁 是 指 文 件 修 订 前 后 的 变 化 ); 通 过 应 用 所 有 的 补 丁, 可 以 重 新 计 算 出 各 个 版 本 的 文 件 内 容 集 中 化 的 版 本 控 制 系 统 接 下 来 人 们 又 遇 到 一 个 问 题, 如 何 让 在 不 同 系 统 上 的 开 发 者 协 同 工 作? 于 是, 集 中 化 的 版 本 控 制 系 统 (Centralized Version Control Systems, 简 称 CVCS) 应 运 而 生 这 类 系 统, 诸 如 CVS Subversion 以 及 Perforce 等, 都 有 一 个 单 一 的 集 中 管 理 的 服 务 器, 保 存 所 有 文 件 的 修 订 版 本, 而 协 同 工 作 的 人 们 都 通 过 客 户 端 连 到 这 台 服 务 器, 取 出 最 新 的 文 件 或 者 提 交 更 新 多 年 以 来, 这 已 成 为 版 本 控 制 系 统 的 标 准 做 法

12 Figure 2. 集 中 化 的 版 本 控 制. 这 种 做 法 带 来 了 许 多 好 处, 特 别 是 相 较 于 老 式 的 本 地 VCS 来 说 现 在, 每 个 人 都 可 以 在 一 定 程 度 上 看 到 项 目 中 的 其 他 人 正 在 做 些 什 么 而 管 理 员 也 可 以 轻 松 掌 控 每 个 开 发 者 的 权 限, 并 且 管 理 一 个 CVCS 要 远 比 在 各 个 客 户 端 上 维 护 本 地 数 据 库 来 得 轻 松 容 易 事 分 两 面, 有 好 有 坏 这 么 做 最 显 而 易 见 的 缺 点 是 中 央 服 务 器 的 单 点 故 障 如 果 宕 机 一 小 时, 那 么 在 这 一 小 时 内, 谁 都 无 法 提 交 更 新, 也 就 无 法 协 同 工 作 如 果 中 心 数 据 库 所 在 的 磁 盘 发 生 损 坏, 又 没 有 做 恰 当 备 份, 毫 无 疑 问 你 将 丢 失 所 有 数 据 包 括 项 目 的 整 个 变 更 历 史, 只 剩 下 人 们 在 各 自 机 器 上 保 留 的 单 独 快 照 本 地 版 本 控 制 系 统 也 存 在 类 似 问 题, 只 要 整 个 项 目 的 历 史 记 录 被 保 存 在 单 一 位 置, 就 有 丢 失 所 有 历 史 更 新 记 录 的 风 险 分 布 式 版 本 控 制 系 统 于 是 分 布 式 版 本 控 制 系 统 (Distributed Version Control System, 简 称 DVCS) 面 世 了 在 这 类 系 统 中, 像 Git Mercurial Bazaar 以 及 Darcs 等, 客 户 端 并 不 只 提 取 最 新 版 本 的 文 件 快 照, 而 是 把 代 码 仓 库 完 整 地 镜 像 下 来 这 么 一 来, 任 何 一 处 协 同 工 作 用 的 服 务 器 发 生 故 障, 事 后 都 可 以 用 任 何 一 个 镜 像 出 来 的 本 地 仓 库 恢 复 因 为 每 一 次 的 克 隆 操 作, 实 际 上 都 是 一 次 对 代 码 仓 库 的 完 整 备 份

13 Figure 3. 分 布 式 版 本 控 制. 更 进 一 步, 许 多 这 类 系 统 都 可 以 指 定 和 若 干 不 同 的 远 端 代 码 仓 库 进 行 交 互 籍 此, 你 就 可 以 在 同 一 个 项 目 中, 分 别 和 不 同 工 作 小 组 的 人 相 互 协 作 你 可 以 根 据 需 要 设 定 不 同 的 协 作 流 程, 比 如 层 次 模 型 式 的 工 作 流, 而 这 在 以 前 的 集 中 式 系 统 中 是 无 法 实 现 的

14 Git 简 史 同 生 活 中 的 许 多 伟 大 事 物 一 样,Git 诞 生 于 一 个 极 富 纷 争 大 举 创 新 的 年 代 Linux 内 核 开 源 项 目 有 着 为 数 众 广 的 参 与 者 绝 大 多 数 的 Linux 内 核 维 护 工 作 都 花 在 了 提 交 补 丁 和 保 存 归 档 的 繁 琐 事 务 上 ( 年 间 ) 到 2002 年, 整 个 项 目 组 开 始 启 用 一 个 专 有 的 分 布 式 版 本 控 制 系 统 BitKeeper 来 管 理 和 维 护 代 码 到 了 2005 年, 开 发 BitKeeper 的 商 业 公 司 同 Linux 内 核 开 源 社 区 的 合 作 关 系 结 束, 他 们 收 回 了 Linux 内 核 社 区 免 费 使 用 BitKeeper 的 权 力 这 就 迫 使 Linux 开 源 社 区 ( 特 别 是 Linux 的 缔 造 者 Linux Torvalds) 基 于 使 用 BitKcheper 时 的 经 验 教 训, 开 发 出 自 己 的 版 本 系 统 他 们 对 新 的 系 统 制 订 了 若 干 目 标 : 速 度 简 单 的 设 计 对 非 线 性 开 发 模 式 的 强 力 支 持 ( 允 许 成 千 上 万 个 并 行 开 发 的 分 支 ) 完 全 分 布 式 有 能 力 高 效 管 理 类 似 Linux 内 核 一 样 的 超 大 规 模 项 目 ( 速 度 和 数 据 量 ) 自 诞 生 于 2005 年 以 来,Git 日 臻 成 熟 完 善, 在 高 度 易 用 的 同 时, 仍 然 保 留 着 初 期 设 定 的 目 标 它 的 速 度 飞 快, 极 其 适 合 管 理 大 项 目, 有 着 令 人 难 以 置 信 的 非 线 性 分 支 管 理 系 统 ( 参 见 Git 分 支 ) Git 基 础 那 么, 简 单 地 说,Git 究 竟 是 怎 样 的 一 个 系 统 呢? 请 注 意 接 下 来 的 内 容 非 常 重 要, 若 你 理 解 了 Git 的 思 想 和 基 本 工 作 原 理, 用 起 来 就 会 知 其 所 以 然, 游 刃 有 余 在 开 始 学 习 Git 的 时 候, 请 努 力 分 清 你 对 其 它 版 本 管 理 系 统 的 已 有 认 识, 如 Subversion 和 Perforce 等 ; 这 么 做 能 帮 助 你 使 用 工 具 时 避 免 发 生 混 淆 Git 在 保 存 和 对 待 各 种 信 息 的 时 候 与 其 它 版 本 控 制 系 统 有 很 大 差 异, 尽 管 操 作 起 来 的 命 令 形 式 非 常 相 近, 理 解 这 些 差 异 将 有 助 于 防 止 你 使 用 中 的 困 惑 直 接 记 录 快 照, 而 非 差 异 比 较 Git 和 其 它 版 本 控 制 系 统 ( 包 括 Subversion 和 近 似 工 具 ) 的 主 要 差 别 在 于 Git 对 待 数 据 的 方 法 概 念 上 来 区 分, 其 它 大 部 分 系 统 以 文 件 变 更 列 表 的 方 式 存 储 信 息 这 类 系 统 (CVS Subversion Perforce Bazaar 等 等 ) 将 它 们 保 存 的 信 息 看 作 是 一 组 基 本 文 件 和 每 个 文 件 随 时 间 逐 步 累 积 的 差 异

15 Figure 4. 存 储 每 个 文 件 与 初 始 版 本 的 差 异. Git 不 按 照 以 上 方 式 对 待 或 保 存 数 据 反 之,Git 更 像 是 把 数 据 看 作 是 对 小 型 文 件 系 统 的 一 组 快 照 每 次 你 提 交 更 新, 或 在 Git 中 保 存 项 目 状 态 时, 它 主 要 对 当 时 的 全 部 文 件 制 作 一 个 快 照 并 保 存 这 个 快 照 的 索 引 为 了 高 效, 如 果 文 件 没 有 修 改,Git 不 再 重 新 存 储 该 文 件, 而 是 只 保 留 一 个 链 接 指 向 之 前 存 储 的 文 件 Git 对 待 数 据 更 像 是 一 个 快 照 流 Figure 5. 存 储 项 目 随 时 间 改 变 的 快 照. 这 是 Git 与 几 乎 所 有 其 它 版 本 控 制 系 统 的 重 要 区 别 因 此 Git 重 新 考 虑 了 以 前 每 一 代 版 本 控 制 系 统 延 续 下 来 的 诸 多 方 面 Git 更 像 是 一 个 小 型 的 文 件 系 统, 提 供 了 许 多 以 此 为 基 础 构 建 的 超 强 工 具, 而 不 只 是 一 个 简 单 的 VCS 稍 后 我 们 在 Git 分 支 讨 论 Git 分 支 管 理 时, 将 探 究 这 种 方 式 对 待 数 据 所 能 获 得 的 益 处 近 乎 所 有 操 作 都 是 本 地 执 行 在 Git 中 的 绝 大 多 数 操 作 都 只 需 要 访 问 本 地 文 件 和 资 源, 一 般 不 需 要 来 自 网 络 上 其 它 计 算 机 的 信 息 如 果 你 习 惯 于 所 有 操 作 都 有 网 络 延 时 开 销 的 集 中 式 版 本 控 制 系 统,Git 在 这 方 面 会 让 你 感 到 速 度 之 神 赐 给 了 Git 超 凡 的 能 量 因 为 你 在 本 地 磁 盘 上 就 有 项 目 的 完 整 历 史, 所 以 大 部 分 操 作 看 起 来 瞬 间 完 成 举 个 例 子, 要 浏 览 项 目 的 历 史,Git 不 需 外 连 到 服 务 器 去 获 取 历 史, 然 后 再 显 示 出 来 它 只 需 直 接 从 本 地 数 据

16 库 中 读 取 你 能 立 即 看 到 项 目 历 史 如 果 你 想 查 看 当 前 版 本 与 一 个 月 前 的 版 本 之 间 引 入 的 修 改,Git 会 查 找 到 一 个 月 前 的 文 件 做 一 次 本 地 的 差 异 计 算, 而 不 是 由 远 程 服 务 器 处 理 或 从 远 程 服 务 器 拉 回 旧 版 本 文 件 再 来 本 地 处 理 这 也 意 味 着 你 离 线 或 者 没 有 VPN 时, 几 乎 可 以 进 行 任 何 操 作 如 你 在 飞 机 或 火 车 上 想 做 些 工 作, 你 能 愉 快 地 提 交, 直 到 有 网 络 连 接 时 再 上 传 如 你 回 家 后 VPN 客 户 端 不 正 常, 你 仍 能 工 作 使 用 其 它 系 统, 做 到 如 此 是 不 可 能 或 很 费 力 的 比 如, 用 Perforce, 你 没 有 连 接 服 务 器 时 几 乎 不 能 做 什 么 事 ; 用 Subversion 和 CVS, 你 能 修 改 文 件, 但 不 能 向 数 据 库 提 交 修 改 ( 因 为 你 的 本 地 数 据 库 离 线 了 ) 这 看 起 来 不 是 大 问 题, 但 是 你 可 能 会 惊 喜 地 发 现 它 带 来 的 巨 大 的 不 同 Git 保 证 完 整 性 Git 中 所 有 数 据 在 存 储 前 都 计 算 校 验 和, 然 后 以 校 验 和 来 引 用 这 意 味 着 不 可 能 在 Git 不 知 情 时 更 改 任 何 文 件 内 容 或 目 录 内 容 这 个 功 能 建 构 在 Git 底 层, 是 构 成 Git 哲 学 不 可 或 缺 的 部 分 若 你 在 传 送 过 程 中 丢 失 信 息 或 损 坏 文 件,Git 就 能 发 现 Git 用 以 计 算 校 验 和 的 机 制 叫 做 SHA-1 散 列 (hash, 哈 希 ) 这 是 一 个 由 40 个 十 六 进 制 字 符 (0-9 和 a-f) 组 成 字 符 串, 基 于 Git 中 文 件 的 内 容 或 目 录 结 构 计 算 出 来 SHA-1 哈 希 看 起 来 是 这 样 : 24b9da aa493b52f8696cd6d3b00373 Git 中 使 用 这 种 哈 希 值 的 情 况 很 多, 你 将 经 常 看 到 这 种 哈 希 值 实 际 上,Git 数 据 库 中 保 存 的 信 息 都 是 以 文 件 内 容 的 哈 希 值 来 索 引, 而 不 是 文 件 名 Git 一 般 只 添 加 数 据 你 执 行 的 Git 操 作, 几 乎 只 往 Git 数 据 库 中 增 加 数 据 很 难 让 Git 执 行 任 何 不 可 逆 操 作, 或 者 让 它 以 任 何 方 式 清 除 数 据 同 别 的 VCS 一 样, 未 提 交 更 新 时 有 可 能 丢 失 或 弄 乱 修 改 的 内 容 ; 但 是 一 旦 你 提 交 快 照 到 Git 中, 就 难 以 再 丢 失 数 据, 特 别 是 如 果 你 定 期 的 推 送 数 据 库 到 其 它 仓 库 的 话 这 使 得 我 们 使 用 Git 成 为 一 个 安 心 愉 悦 的 过 程, 因 为 我 们 深 知 可 以 尽 情 做 各 种 尝 试, 而 没 有 把 事 情 弄 糟 的 危 险 更 深 度 探 讨 Git 如 何 保 存 数 据 及 恢 复 丢 失 数 据 的 话 题, 请 参 考 撤 消 操 作 三 种 状 态 好, 请 注 意 如 果 你 希 望 后 面 的 学 习 更 顺 利, 记 住 下 面 这 些 关 于 Git 的 概 念 Git 有 三 种 状 态, 你 的 文 件 可 能 处 于 其 中 之 一 : 已 提 交 (committed) 已 修 改 (modified) 和 已 暂 存 (staged) 已 提 交 表 示 数 据 已 经 安 全 的 保 存 在 本 地 数 据 库 中 已 修 改 表 示 修 改 了 文 件, 但 还 没 保 存 到 数 据 库 中 已 暂 存 表 示 对 一 个 已 修 改 文 件 的 当 前 版 本 做 了 标 记, 使 之 包 含 在 下 次 提 交 的 快 照 中 由 此 引 入 Git 项 目 的 三 个 工 作 区 域 的 概 念 :Git 仓 库 工 作 目 录 以 及 暂 存 区 域

17 Figure 6. 工 作 目 录 暂 存 区 域 以 及 Git 仓 库. Git 仓 库 目 录 是 Git 用 来 保 存 项 目 的 元 数 据 和 对 象 数 据 库 的 地 方 这 是 Git 中 最 重 要 的 部 分, 从 其 它 计 算 机 克 隆 仓 库 时, 拷 贝 的 就 是 这 里 的 数 据 工 作 目 录 是 对 项 目 的 某 个 版 本 独 立 提 取 出 来 的 内 容 这 些 从 Git 仓 库 的 压 缩 数 据 库 中 提 取 出 来 的 文 件, 放 在 磁 盘 上 供 你 使 用 或 修 改 暂 存 区 域 是 一 个 文 件, 保 存 了 下 次 将 提 交 的 文 件 列 表 信 息, 一 般 在 Git 仓 库 目 录 中 有 时 候 也 被 称 作 ` 索 引 ', 不 过 一 般 说 法 还 是 叫 暂 存 区 域 基 本 的 Git 工 作 流 程 如 下 : 1. 在 工 作 目 录 中 修 改 文 件 2. 暂 存 文 件, 将 文 件 的 快 照 放 入 暂 存 区 域 3. 提 交 更 新, 找 到 暂 存 区 域 的 文 件, 将 快 照 永 久 性 存 储 到 Git 仓 库 目 录 如 果 Git 目 录 中 保 存 着 的 特 定 版 本 文 件, 就 属 于 已 提 交 状 态 如 果 作 了 修 改 并 已 放 入 暂 存 区 域, 就 属 于 已 暂 存 状 态 如 果 自 上 次 取 出 后, 作 了 修 改 但 还 没 有 放 到 暂 存 区 域, 就 是 已 修 改 状 态 在 Git 基 础 一 章, 你 会 进 一 步 了 解 这 些 状 态 的 细 节, 并 学 会 如 何 根 据 文 件 状 态 实 施 后 续 操 作, 以 及 怎 样 跳 过 暂 存 直 接 提 交 命 令 行 Git 有 多 种 使 用 方 式 你 可 以 使 用 原 生 的 命 令 行 模 式, 也 可 以 使 用 GUI 模 式, 这 些 GUI 软 件 也 能 提 供 多 种 功 能 在 本 书 中, 我 们 将 使 用 命 令 行 模 式 这 是 因 为 首 先, 只 有 在 命 令 行 模 式 下 你 才 能 执 行 Git 的 所 有 命 令, 而 大 多 数 的 GUI 软 件 只 实 现 了 Git 所 有 功 能 的 一 个 子 集 以 降 低 操 作 难 度 如 果 你 学 会 了 在 命 令 行 下 如 何 操 作, 那 么 你 在

18 操 作 GUI 软 件 时 应 该 也 不 会 遇 到 什 么 困 难, 但 是, 反 之 则 不 成 立 此 外, 由 于 每 个 人 的 想 法 与 侧 重 点 不 同, 不 同 的 人 常 常 会 安 装 不 同 的 GUI 软 件, 但 所 有 人 一 定 会 有 命 令 行 工 具 假 如 你 是 Mac 用 户, 我 们 希 望 你 懂 得 如 何 使 用 终 端 (Terminal); 假 如 你 是 Windows 用 户, 我 们 希 望 你 懂 得 如 何 使 用 命 令 窗 口 (Command Prompt) 或 PowerShell 如 果 你 尚 未 掌 握 以 上 技 能, 我 们 建 议 你 先 停 下 来 快 速 学 习 一 下, 本 书 中 的 讲 述 和 举 例 将 用 到 这 些 技 能 安 装 Git 在 你 开 始 使 用 Git 前, 需 要 将 它 安 装 在 你 的 计 算 机 上 即 便 已 经 安 装, 最 好 将 它 升 级 到 最 新 的 版 本 你 可 以 通 过 软 件 包 或 者 其 它 安 装 程 序 来 安 装, 或 者 下 载 源 码 编 译 安 装 NOTE 本 书 写 作 时 使 用 的 Git 版 本 为 我 们 使 用 的 大 部 分 命 令 仍 然 可 以 在 很 古 老 的 Git 版 本 上 使 用, 但 也 有 少 部 分 命 令 不 好 用 或 者 在 旧 版 本 中 的 行 为 有 差 异 因 为 Git 在 保 持 向 后 兼 容 方 便 表 现 很 好, 本 书 使 用 的 这 些 命 令 在 2.0 之 后 的 版 本 应 该 有 效 在 Linux 上 安 装 如 果 你 想 在 Linux 上 用 二 进 制 安 装 程 序 来 安 装 Git, 可 以 使 用 发 行 版 包 含 的 基 础 软 件 包 管 理 工 具 来 安 装 如 果 以 Fedora 上 为 例, 你 可 以 使 用 yum: $ sudo yum install git 如 果 你 在 基 于 Debian 的 发 行 版 上, 请 尝 试 用 apt-get: $ sudo apt-get install git 要 了 解 更 多 选 择,Git 官 方 网 站 上 有 在 各 种 Unix 风 格 的 系 统 上 安 装 步 骤, 网 址 为 在 Mac 上 安 装 在 Mac 上 安 装 Git 有 多 种 方 式 最 简 单 的 方 法 是 安 装 Xcode Command Line Tools Mavericks (10.9) 或 更 高 版 本 的 系 统 中, 在 Terminal 里 尝 试 首 次 运 行 git 命 令 即 可 如 果 没 有 安 装 过 命 令 行 开 发 者 工 具, 将 会 提 示 你 安 装 如 果 你 想 安 装 更 新 的 版 本, 可 以 使 用 二 进 制 安 装 程 序 官 方 维 护 的 OSX Git 安 装 程 序 可 以 在 Git 官 方 网 站 下 载, 网 址 为

19 Figure 7. Git OS X 安 装 程 序. 你 也 可 以 将 它 作 为 GitHub for Mac 的 一 部 分 来 安 装 它 们 的 图 形 化 Git 工 具 有 一 个 安 装 命 令 行 工 具 的 选 项 你 可 以 从 GitHub for Mac 网 站 下 载 该 工 具, 网 址 为 在 Windows 上 安 装 在 Windows 上 安 装 Git 也 有 几 种 安 装 方 法 官 方 版 本 可 以 在 Git 官 方 网 站 下 载 打 开 下 载 会 自 动 开 始 要 注 意 这 是 一 个 名 为 Git for Windows 的 项 目 ( 也 叫 做 msysgit), 和 Git 是 分 别 独 立 的 项 目 ; 更 多 信 息 请 访 问 另 一 个 简 单 的 方 法 是 安 装 GitHub for Windows 该 安 装 程 序 包 含 图 形 化 和 命 令 行 版 本 的 Git 它 也 能 支 持 Powershell, 提 供 了 稳 定 的 凭 证 缓 存 和 健 全 的 CRLF 设 置 稍 后 我 们 会 对 这 方 面 有 更 多 了 解, 现 在 只 要 一 句 话 就 够 了, 这 些 都 是 你 所 需 要 的 你 可 以 在 GitHub for Windows 网 站 下 载, 网 址 为 从 源 代 码 安 装 有 人 觉 得 从 源 码 安 装 Git 更 实 用, 因 为 你 能 得 到 最 新 的 版 本 二 进 制 安 装 程 序 倾 向 于 有 一 些 滞 后, 当 然 近 几 年 Git 已 经 成 熟, 这 个 差 异 不 再 显 著 如 果 你 想 从 源 码 安 装 Git, 需 要 安 装 Git 依 赖 的 库 :curl zlib openssl expat, 还 有 libiconv 如 果 你 的 系 统 上 有 yum ( 如 Fedora) 或 者 apt-get( 如 基 于 Debian 的 系 统 ), 可 以 使 用 以 下 命 令 之 一 来 安 装 最 小 化 的 依 赖

20 包 来 编 译 和 安 装 Git 的 二 进 制 版 : $ sudo yum install curl-devel expat-devel gettext-devel \ openssl-devel zlib-devel $ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \ libz-dev libssl-dev 为 了 能 够 添 加 更 多 格 式 的 文 档 ( 如 doc, html, info), 你 需 要 安 装 以 下 的 依 赖 包 : $ sudo yum install asciidoc xmlto docbook2x $ sudo apt-get install asciidoc xmlto docbook2x 当 你 安 装 好 所 有 的 必 要 依 赖, 你 可 以 继 续 从 几 个 地 方 来 取 得 最 新 发 布 版 本 的 tar 包 你 可 以 从 Kernel.org 网 站 获 取, 网 址 为 或 从 GitHub 网 站 上 的 镜 像 来 获 得, 网 址 为 通 常 在 GitHub 上 的 是 最 新 版 本, 但 kernel.org 上 包 含 有 文 件 下 载 签 名, 如 果 你 想 验 证 下 载 正 确 性 的 话 会 用 到 接 着, 编 译 并 安 装 : $ tar -zxf git tar.gz $ cd git $ make configure $./configure --prefix=/usr $ make all doc info $ sudo make install install-doc install-html install-info 完 成 后, 你 可 以 使 用 Git 来 获 取 Git 的 升 级 : $ git clone git://git.kernel.org/pub/scm/git/git.git 初 次 运 行 Git 前 的 配 置 既 然 已 经 在 系 统 上 安 装 了 Git, 你 会 想 要 做 几 件 事 来 定 制 你 的 Git 环 境 每 台 计 算 机 上 只 需 要 配 置 一 次, 程 序 升 级 时 会 保 留 配 置 信 息 你 可 以 在 任 何 时 候 再 次 通 过 运 行 命 令 来 修 改 它 们 Git 自 带 一 个 git config 的 工 具 来 帮 助 设 置 控 制 Git 外 观 和 行 为 的 配 置 变 量 这 些 变 量 存 储 在 三 个 不 同 的 位 置 : 1. /etc/gitconfig 文 件 : 包 含 系 统 上 每 一 个 用 户 及 他 们 仓 库 的 通 用 配 置 如 果 使 用 带 有 --system 选 项 的 git config 时, 它 会 从 此 文 件 读 写 配 置 变 量

21 2. ~/.gitconfig 或 ~/.config/git/config 文 件 : 只 针 对 当 前 用 户 可 以 传 递 --global 选 项 让 Git 读 写 此 文 件 3. 当 前 使 用 仓 库 的 Git 目 录 中 的 config 文 件 ( 就 是.git/config): 针 对 该 仓 库 每 一 个 级 别 覆 盖 上 一 级 别 的 配 置, 所 以.git/config 的 配 置 变 量 会 覆 盖 /etc/gitconfig 中 的 配 置 变 量 在 Windows 系 统 中,Git 会 查 找 $HOME 目 录 下 ( 一 般 情 况 下 是 C:\Users\$USER) 的.gitconfig 文 件 Git 同 样 也 会 寻 找 /etc/gitconfig 文 件, 但 只 限 于 MSys 的 根 目 录 下, 即 安 装 Git 时 所 选 的 目 标 位 置 用 户 信 息 当 安 装 完 Git 应 该 做 的 第 一 件 事 就 是 设 置 你 的 用 户 名 称 与 邮 件 地 址 这 样 做 很 重 要, 因 为 每 一 个 Git 的 提 交 都 会 使 用 这 些 信 息, 并 且 它 会 写 入 到 你 的 每 一 次 提 交 中, 不 可 更 改 : $ git config --global user.name "John Doe" $ git config --global user. johndoe@example.com 再 次 强 调, 如 果 使 用 了 --global 选 项, 那 么 该 命 令 只 需 要 运 行 一 次, 因 为 之 后 无 论 你 在 该 系 统 上 做 任 何 事 情, Git 都 会 使 用 那 些 信 息 当 你 想 针 对 特 定 项 目 使 用 不 同 的 用 户 名 称 与 邮 件 地 址 时, 可 以 在 那 个 项 目 目 录 下 运 行 没 有 --global 选 项 的 命 令 来 配 置 很 多 GUI 工 具 都 会 在 第 一 次 运 行 时 帮 助 你 配 置 这 些 信 息 文 本 编 辑 器 既 然 用 户 信 息 已 经 设 置 完 毕, 你 可 以 配 置 默 认 文 本 编 辑 器 了, 当 Git 需 要 你 输 入 信 息 时 会 调 用 它 如 果 未 配 置,Git 会 使 用 操 作 系 统 默 认 的 文 本 编 辑 器, 通 常 是 Vim 如 果 你 想 使 用 不 同 的 文 本 编 辑 器, 例 如 Emacs, 可 以 这 样 做 : $ git config --global core.editor emacs WARNING Vim 和 Emacs 是 像 Linux 与 Mac 等 基 于 Unix 的 系 统 上 开 发 者 经 常 使 用 的 流 行 的 文 本 编 辑 器 如 果 你 对 这 些 编 辑 器 都 不 是 很 了 解 或 者 你 使 用 的 是 Windows 系 统, 那 么 可 能 需 要 搜 索 如 何 在 Git 中 配 置 你 最 常 用 的 编 辑 器 如 果 你 不 设 置 编 辑 器 并 且 不 知 道 Vim 或 Emacs 是 什 么, 当 它 们 运 行 起 来 后 你 可 能 会 被 弄 糊 涂 不 知 所 措 检 查 配 置 信 息 如 果 想 要 检 查 你 的 配 置, 可 以 使 用 git config --list 命 令 来 列 出 所 有 Git 当 时 能 找 到 的 配 置

22 $ git config --list user.name=john Doe color.status=auto color.branch=auto color.interactive=auto color.diff=auto... 你 可 能 会 看 到 重 复 的 变 量 名, 因 为 Git 会 从 不 同 的 文 件 中 读 取 同 一 个 配 置 ( 例 如 :/etc/gitconfig 与 ~/.gitconfig) 这 种 情 况 下,Git 会 使 用 它 找 到 的 每 一 个 变 量 的 最 后 一 个 配 置 你 可 以 通 过 输 入 git config <key>: 来 检 查 Git 的 某 一 项 配 置 $ git config user.name John Doe 获 取 帮 助 若 你 使 用 Git 时 需 要 获 取 帮 助, 有 三 种 方 法 可 以 找 到 Git 命 令 的 使 用 手 册 : $ git help <verb> $ git <verb> --help $ man git-<verb> 例 如, 要 想 获 得 config 命 令 的 手 册, 执 行 $ git help config 这 些 命 令 很 棒, 因 为 你 随 时 随 地 可 以 使 用 而 无 需 联 网 如 果 你 觉 得 手 册 或 者 本 书 的 内 容 还 不 够 用, 你 可 以 尝 试 在 Freenode IRC 服 务 器 ( irc.freenode.net ) 的 #git 或 #github 频 道 寻 求 帮 助 这 些 频 道 经 常 有 上 百 人 在 线, 他 们 都 精 通 Git 并 且 乐 于 助 人 总 结 你 应 该 已 经 对 Git 是 什 么 Git 与 你 可 能 正 在 使 用 的 集 中 式 版 本 控 制 系 统 有 何 区 别 等 问 题 有 了 基 本 的 了 解 现 在, 在 你 的 个 人 系 统 中 应 该 也 有 了 一 份 能 够 工 作 的 Git 版 本 是 时 候 开 始 学 习 有 关 Git 的 基 础 知 识 了

23 Git 基 础 假 如 你 只 能 阅 读 一 章 来 学 习 Git, 本 章 就 是 你 的 不 二 选 择 本 章 内 容 涵 盖 你 在 使 用 Git 完 成 各 种 工 作 中 将 要 使 用 的 各 种 基 本 命 令 在 学 习 完 本 章 之 后, 你 应 该 能 够 配 置 并 初 始 化 一 个 仓 库 (repository) 开 始 或 停 止 跟 踪 (track) 文 件 暂 存 (stage) 或 提 交 (commit) 更 改 本 章 也 将 向 你 演 示 如 何 配 置 Git 来 忽 略 指 定 的 文 件 和 文 件 模 式 如 何 迅 速 而 简 单 地 撤 销 错 误 操 作 如 何 浏 览 你 的 项 目 的 历 史 版 本 以 及 不 同 提 交 (commits) 间 的 差 异 如 何 向 你 的 远 程 仓 库 推 送 (push) 以 及 如 何 从 你 的 远 程 仓 库 拉 取 (pull) 文 件 获 取 Git 仓 库 有 两 种 取 得 Git 项 目 仓 库 的 方 法 第 一 种 是 在 现 有 项 目 或 目 录 下 导 入 所 有 文 件 到 Git 中 ; 第 二 种 是 从 一 个 服 务 器 克 隆 一 个 现 有 的 Git 仓 库 在 现 有 目 录 中 初 始 化 仓 库 如 果 你 打 算 使 用 Git 来 对 现 有 的 项 目 进 行 管 理, 你 只 需 要 进 入 该 项 目 目 录 并 输 入 : $ git init 该 命 令 将 创 建 一 个 名 为.git 的 子 目 录, 这 个 子 目 录 含 有 你 初 始 化 的 Git 仓 库 中 所 有 的 必 须 文 件, 这 些 文 件 是 Git 仓 库 的 骨 干 但 是, 在 这 个 时 候, 我 们 仅 仅 是 做 了 一 个 初 始 化 的 操 作, 你 的 项 目 里 的 文 件 还 没 有 被 跟 踪 ( 参 见 Git 内 部 原 理 来 了 解 更 多 关 于 到 底.git 文 件 夹 中 包 含 了 哪 些 文 件 的 信 息 ) 如 果 你 是 在 一 个 已 经 存 在 文 件 的 文 件 夹 ( 而 不 是 空 文 件 夹 ) 中 初 始 化 Git 仓 库 来 进 行 版 本 控 制 的 话, 你 应 该 开 始 跟 踪 这 些 文 件 并 提 交 你 可 通 过 git add 命 令 来 实 现 对 指 定 文 件 的 跟 踪, 然 后 执 行 git commit 提 交 : $ git add *.c $ git add LICENSE $ git commit -m 'initial project version' 稍 后 我 们 再 逐 一 解 释 每 一 条 指 令 的 意 思 现 在, 你 已 经 得 到 了 一 个 实 际 维 护 ( 或 者 说 是 跟 踪 ) 着 若 干 个 文 件 的 Git 仓 库 克 隆 现 有 的 仓 库 如 果 你 想 获 得 一 份 已 经 存 在 了 的 Git 仓 库 的 拷 贝, 比 如 说, 你 想 为 某 个 开 源 项 目 贡 献 自 己 的 一 份 力, 这 时 就 要 用 到 git clone 命 令 如 果 你 对 其 它 的 VCS 系 统 ( 比 如 说 Subversion) 很 熟 悉, 请 留 心 一 下 你 所 使 用 的 命 令 是 "clone" 而 不 是 "checkout" 这 是 Git 区 别 于 其 它 版 本 控 制 系 统 的 一 个 重 要 特 性,Git 克 隆 的 是 该 Git 仓 库 服 务 器 上 的 几 乎 所 有 数 据, 而 不 是 仅 仅 复 制 完 成 你 的 工 作 所 需 要 文 件 当 你 执 行 git clone 命 令 的 时 候, 默 认 配 置 下 远 程 Git 仓 库 中 的 每 一 个 文 件 的 每 一 个 版 本 都 将 被 拉 取 下 来 事 实 上, 如 果 你 的 服 务 器 的 磁 盘 坏 掉 了, 你 通 常 可 以 使 用 任 何 一 个 克 隆 下 来 的 用 户 端 来 重 建 服 务 器 上 的 仓 库 ( 虽 然 可 能 会 丢 失 某 些 服 务 器 端 的 挂 钩 设 置, 但 是 所 有 版 本 的 数 据 仍 在, 详 见 在 服 务 器 上 搭 建 Git )

24 克 隆 仓 库 的 命 令 格 式 是 git clone [url] 比 如, 要 克 隆 Git 的 可 链 接 库 libgit2, 可 以 用 下 面 的 命 令 : $ git clone 这 会 在 当 前 目 录 下 创 建 一 个 名 为 libgit2 的 目 录, 并 在 这 个 目 录 下 初 始 化 一 个.git 文 件 夹, 从 远 程 仓 库 拉 取 下 所 有 数 据 放 入.git 文 件 夹, 然 后 从 中 读 取 最 新 版 本 的 文 件 的 拷 贝 如 果 你 进 入 到 这 个 新 建 的 libgit2 文 件 夹, 你 会 发 现 所 有 的 项 目 文 件 已 经 在 里 面 了, 准 备 就 绪 等 待 后 续 的 开 发 和 使 用 如 果 你 想 在 克 隆 远 程 仓 库 的 时 候, 自 定 义 本 地 仓 库 的 名 字, 你 可 以 使 用 如 下 命 令 : $ git clone mylibgit 这 将 执 行 与 上 一 个 命 令 相 同 的 操 作, 不 过 在 本 地 创 建 的 仓 库 名 字 变 为 mylibgit Git 支 持 多 种 数 据 传 输 协 议 上 面 的 例 子 使 用 的 是 协 议, 不 过 你 也 可 以 使 用 git:// 协 议 或 者 使 用 SSH 传 输 协 议, 比 如 user@server:path/to/repo.git 在 服 务 器 上 搭 建 Git 将 会 介 绍 所 有 这 些 协 议 在 服 务 器 端 如 何 配 置 使 用, 以 及 各 种 方 式 之 间 的 利 弊 记 录 每 次 更 新 到 仓 库 现 在 我 们 手 上 有 了 一 个 真 实 项 目 的 Git 仓 库, 并 从 这 个 仓 库 中 取 出 了 所 有 文 件 的 工 作 拷 贝 接 下 来, 对 这 些 文 件 做 些 修 改, 在 完 成 了 一 个 阶 段 的 目 标 之 后, 提 交 本 次 更 新 到 仓 库 请 记 住, 你 工 作 目 录 下 的 每 一 个 文 件 都 不 外 乎 这 两 种 状 态 : 已 跟 踪 或 未 跟 踪 已 跟 踪 的 文 件 是 指 那 些 被 纳 入 了 版 本 控 制 的 文 件, 在 上 一 次 快 照 中 有 它 们 的 记 录, 在 工 作 一 段 时 间 后, 它 们 的 状 态 可 能 处 于 未 修 改, 已 修 改 或 已 放 入 暂 存 区 工 作 目 录 中 除 已 跟 踪 文 件 以 外 的 所 有 其 它 文 件 都 属 于 未 跟 踪 文 件, 它 们 既 不 存 在 于 上 次 快 照 的 记 录 中, 也 没 有 放 入 暂 存 区 初 次 克 隆 某 个 仓 库 的 时 候, 工 作 目 录 中 的 所 有 文 件 都 属 于 已 跟 踪 文 件, 并 处 于 未 修 改 状 态 编 辑 过 某 些 文 件 之 后, 由 于 自 上 次 提 交 后 你 对 它 们 做 了 修 改,Git 将 它 们 标 记 为 已 修 改 文 件 我 们 逐 步 将 这 些 修 改 过 的 文 件 放 入 暂 存 区, 然 后 提 交 所 有 暂 存 了 的 修 改, 如 此 反 复 所 以 使 用 Git 时 文 件 的 生 命 周 期 如 下 :

25 Figure 8. 文 件 的 状 态 变 化 周 期 检 查 当 前 文 件 状 态 要 查 看 哪 些 文 件 处 于 什 么 状 态, 可 以 用 git status 命 令 如 果 在 克 隆 仓 库 后 立 即 使 用 此 命 令, 会 看 到 类 似 这 样 的 输 出 : $ git status On branch master nothing to commit, working directory clean 这 说 明 你 现 在 的 工 作 目 录 相 当 干 净 换 句 话 说, 所 有 已 跟 踪 文 件 在 上 次 提 交 后 都 未 被 更 改 过 此 外, 上 面 的 信 息 还 表 明, 当 前 目 录 下 没 有 出 现 任 何 处 于 未 跟 踪 状 态 的 新 文 件, 否 则 Git 会 在 这 里 列 出 来 最 后, 该 命 令 还 显 示 了 当 前 所 在 分 支, 并 告 诉 你 这 个 分 支 同 远 程 服 务 器 上 对 应 的 分 支 没 有 偏 离 现 在, 分 支 名 是 master, 这 是 默 认 的 分 支 名 我 们 在 Git 分 支 会 详 细 讨 论 分 支 和 引 用 现 在, 让 我 们 在 项 目 下 创 建 一 个 新 的 README 文 件 如 果 之 前 并 不 存 在 这 个 文 件, 使 用 git status 命 令, 你 将 看 到 一 个 新 的 未 跟 踪 文 件 : $ echo 'My Project' > README $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) README nothing added to commit but untracked files present (use "git add" to track)

26 在 状 态 报 告 中 可 以 看 到 新 建 的 README 文 件 出 现 在 Untracked files 下 面 未 跟 踪 的 文 件 意 味 着 Git 在 之 前 的 快 照 ( 提 交 ) 中 没 有 这 些 文 件 ;Git 不 会 自 动 将 之 纳 入 跟 踪 范 围, 除 非 你 明 明 白 白 地 告 诉 它 我 需 要 跟 踪 该 文 件, 这 样 的 处 理 让 你 不 必 担 心 将 生 成 的 二 进 制 文 件 或 其 它 不 想 被 跟 踪 的 文 件 包 含 进 来 不 过 现 在 的 例 子 中, 我 们 确 实 想 要 跟 踪 管 理 README 这 个 文 件 跟 踪 新 文 件 使 用 命 令 git add 开 始 跟 踪 一 个 文 件 所 以, 要 跟 踪 README 文 件, 运 行 : $ git add README 此 时 再 运 行 git status 命 令, 会 看 到 README 文 件 已 被 跟 踪, 并 处 于 暂 存 状 态 : $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README 只 要 在 Changes to be committed 这 行 下 面 的, 就 说 明 是 已 暂 存 状 态 如 果 此 时 提 交, 那 么 该 文 件 此 时 此 刻 的 版 本 将 被 留 存 在 历 史 记 录 中 你 可 能 会 想 起 之 前 我 们 使 用 git init 后 就 运 行 了 git add (files) 命 令, 开 始 跟 踪 当 前 目 录 下 的 文 件 git add 命 令 使 用 文 件 或 目 录 的 路 径 作 为 参 数 ; 如 果 参 数 是 目 录 的 路 径, 该 命 令 将 递 归 地 跟 踪 该 目 录 下 的 所 有 文 件 暂 存 已 修 改 文 件 现 在 我 们 来 修 改 一 个 已 被 跟 踪 的 文 件 如 果 你 修 改 了 一 个 名 为 CONTRIBUTING.md 的 已 被 跟 踪 的 文 件, 然 后 运 行 git status 命 令, 会 看 到 下 面 内 容 : $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md

27 文 件 CONTRIBUTING.md 出 现 在 Changes not staged for commit 这 行 下 面, 说 明 已 跟 踪 文 件 的 内 容 发 生 了 变 化, 但 还 没 有 放 到 暂 存 区 要 暂 存 这 次 更 新, 需 要 运 行 git add 命 令 这 是 个 多 功 能 命 令 : 可 以 用 它 开 始 跟 踪 新 文 件, 或 者 把 已 跟 踪 的 文 件 放 到 暂 存 区, 还 能 用 于 合 并 时 把 有 冲 突 的 文 件 标 记 为 已 解 决 状 态 等 将 这 个 命 令 理 解 为 添 加 内 容 到 下 一 次 提 交 中 而 不 是 将 一 个 文 件 添 加 到 项 目 中 要 更 加 合 适 现 在 让 我 们 运 行 git add 将 "CONTRIBUTING.md" 放 到 暂 存 区, 然 后 再 看 看 git status 的 输 出 : $ git add CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: modified: README CONTRIBUTING.md 现 在 两 个 文 件 都 已 暂 存, 下 次 提 交 时 就 会 一 并 记 录 到 仓 库 假 设 此 时, 你 想 要 在 CONTRIBUTING.md 里 再 加 条 注 释, 重 新 编 辑 存 盘 后, 准 备 好 提 交 不 过 且 慢, 再 运 行 git status 看 看 : $ vim CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: modified: README CONTRIBUTING.md Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md 怎 么 回 事? 现 在 CONTRIBUTING.md 文 件 同 时 出 现 在 暂 存 区 和 非 暂 存 区 这 怎 么 可 能 呢? 好 吧, 实 际 上 Git 只 不 过 暂 存 了 你 运 行 git add 命 令 时 的 版 本, 如 果 你 现 在 提 交,CONTRIBUTING.md 的 版 本 是 你 最 后 一 次 运 行 git add 命 令 时 的 那 个 版 本, 而 不 是 你 运 行 git commit 时, 在 工 作 目 录 中 的 当 前 版 本 所 以, 运 行 了 git add 之 后 又 作 了 修 订 的 文 件, 需 要 重 新 运 行 git add 把 最 新 版 本 重 新 暂 存 起 来 :

28 $ git add CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: modified: README CONTRIBUTING.md 状 态 简 览 git status 命 令 的 输 出 十 分 详 细, 但 其 用 语 有 些 繁 琐 如 果 你 使 用 git status -s 命 令 或 git status --short 命 令, 你 将 得 到 一 种 更 为 紧 凑 的 格 式 输 出 运 行 git status -s, 状 态 报 告 输 出 如 下 : $ git status -s M README MM Rakefile A lib/git.rb M lib/simplegit.rb?? LICENSE.txt 新 添 加 的 未 跟 踪 文 件 前 面 有?? 标 记, 新 添 加 到 暂 存 区 中 的 文 件 前 面 有 A 标 记, 修 改 过 的 文 件 前 面 有 M 标 记 你 可 能 注 意 到 了 M 有 两 个 可 以 出 现 的 位 置, 出 现 在 右 边 的 M 表 示 该 文 件 被 修 改 了 但 是 还 没 放 入 暂 存 区, 出 现 在 靠 左 边 的 M 表 示 该 文 件 被 修 改 了 并 放 入 了 暂 存 区 例 如, 上 面 的 状 态 报 告 显 示 : README 文 件 在 工 作 区 被 修 改 了 但 是 还 没 有 将 修 改 后 的 文 件 放 入 暂 存 区,lib/simplegit.rb 文 件 被 修 改 了 并 将 修 改 后 的 文 件 放 入 了 暂 存 区 而 Rakefile 在 工 作 区 被 修 改 并 提 交 到 暂 存 区 后 又 在 工 作 区 中 被 修 改 了, 所 以 在 暂 存 区 和 工 作 区 都 有 该 文 件 被 修 改 了 的 记 录 忽 略 文 件 一 般 我 们 总 会 有 些 文 件 无 需 纳 入 Git 的 管 理, 也 不 希 望 它 们 总 出 现 在 未 跟 踪 文 件 列 表 通 常 都 是 些 自 动 生 成 的 文 件, 比 如 日 志 文 件, 或 者 编 译 过 程 中 创 建 的 临 时 文 件 等 在 这 种 情 况 下, 我 们 可 以 创 建 一 个 名 为.gitignore 的 文 件, 列 出 要 忽 略 的 文 件 模 式 来 看 一 个 实 际 的 例 子 : $ cat.gitignore *.[oa] *~ 第 一 行 告 诉 Git 忽 略 所 有 以.o 或.a 结 尾 的 文 件 一 般 这 类 对 象 文 件 和 存 档 文 件 都 是 编 译 过 程 中 出 现 的 第 二 行 告 诉 Git 忽 略 所 有 以 波 浪 符 (~) 结 尾 的 文 件, 许 多 文 本 编 辑 软 件 ( 比 如 Emacs) 都 用 这 样 的 文 件 名 保 存 副 本 此 外, 你 可 能 还 需 要 忽 略 log,tmp 或 者 pid 目 录, 以 及 自 动 生 成 的 文 档 等 等 要 养 成 一 开 始 就 设 置 好.gitignore 文 件 的 习 惯, 以 免 将 来 误 提 交 这 类 无 用 的 文 件

29 文 件.gitignore 的 格 式 规 范 如 下 : 所 有 空 行 或 者 以 # 开 头 的 行 都 会 被 Git 忽 略 可 以 使 用 标 准 的 glob 模 式 匹 配 匹 配 模 式 可 以 以 (/) 开 头 防 止 递 归 匹 配 模 式 可 以 以 (/) 结 尾 指 定 目 录 要 忽 略 指 定 模 式 以 外 的 文 件 或 目 录, 可 以 在 模 式 前 加 上 惊 叹 号 (!) 取 反 所 谓 的 glob 模 式 是 指 shell 所 使 用 的 简 化 了 的 正 则 表 达 式 星 号 (*) 匹 配 零 个 或 多 个 任 意 字 符 ;[abc] 匹 配 任 何 一 个 列 在 方 括 号 中 的 字 符 ( 这 个 例 子 要 么 匹 配 一 个 a, 要 么 匹 配 一 个 b, 要 么 匹 配 一 个 c); 问 号 (?) 只 匹 配 一 个 任 意 字 符 ; 如 果 在 方 括 号 中 使 用 短 划 线 分 隔 两 个 字 符, 表 示 所 有 在 这 两 个 字 符 范 围 内 的 都 可 以 匹 配 ( 比 如 [0-9] 表 示 匹 配 所 有 0 到 9 的 数 字 ) 使 用 两 个 星 号 (*) 表 示 匹 配 任 意 中 间 目 录, 比 如 `a/**/z` 可 以 匹 配 a/z, a/b/z 或 `a/b/c/z` 等 我 们 再 看 一 个.gitignore 文 件 的 例 子 : # no.a files *.a # but do track lib.a, even though you're ignoring.a files above!lib.a # only ignore the TODO file in the current directory, not subdir/todo /TODO # ignore all files in the build/ directory build/ # ignore doc/notes.txt, but not doc/server/arch.txt doc/*.txt # ignore all.pdf files in the doc/ directory doc/**/*.pdf TIP GitHub 有 一 个 十 分 详 细 的 针 对 数 十 种 项 目 及 语 言 的.gitignore 文 件 列 表, 你 可 以 在 找 到 它. 查 看 已 暂 存 和 未 暂 存 的 修 改 如 果 git status 命 令 的 输 出 对 于 你 来 说 过 于 模 糊, 你 想 知 道 具 体 修 改 了 什 么 地 方, 可 以 用 git diff 命 令 稍 后 我 们 会 详 细 介 绍 git diff, 你 可 能 通 常 会 用 它 来 回 答 这 两 个 问 题 : 当 前 做 的 哪 些 更 新 还 没 有 暂 存? 有 哪 些 更 新 已 经 暂 存 起 来 准 备 好 了 下 次 提 交? 尽 管 git status 已 经 通 过 在 相 应 栏 下 列 出 文 件 名 的 方 式 回 答 了 这 个 问 题,git diff 将 通 过 文 件 补 丁 的 格 式 显 示 具 体 哪 些 行 发 生 了 改 变

30 假 如 再 次 修 改 README 文 件 后 暂 存, 然 后 编 辑 CONTRIBUTING.md 文 件 后 先 不 暂 存, 运 行 status 命 令 将 会 看 到 : $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md 要 查 看 尚 未 暂 存 的 文 件 更 新 了 哪 些 部 分, 不 加 参 数 直 接 输 入 git diff: $ git diff diff --git a/contributing.md b/contributing.md index 8ebb e24f a/contributing.md ,7 branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change -merged in. +merged in. Also, split your changes into comprehensive chunks if your patch is +longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's 此 命 令 比 较 的 是 工 作 目 录 中 当 前 文 件 和 暂 存 区 域 快 照 之 间 的 差 异, 也 就 是 修 改 之 后 还 没 有 暂 存 起 来 的 变 化 内 容 若 要 查 看 已 暂 存 的 将 要 添 加 到 下 次 提 交 里 的 内 容, 可 以 用 git diff --cached 命 令 (Git 及 更 高 版 本 还 允 许 使 用 git diff --staged, 效 果 是 相 同 的, 但 更 好 记 些 )

31 $ git diff --staged diff --git a/readme b/readme new file mode index a1 --- /dev/null +++ b/readme -0,0 +1 +My Project 请 注 意,git diff 本 身 只 显 示 尚 未 暂 存 的 改 动, 而 不 是 自 上 次 提 交 以 来 所 做 的 所 有 改 动 所 以 有 时 候 你 一 下 子 暂 存 了 所 有 更 新 过 的 文 件 后, 运 行 git diff 后 却 什 么 也 没 有, 就 是 这 个 原 因 像 之 前 说 的, 暂 存 CONTRIBUTING.md 后 再 编 辑, 运 行 git status 会 看 到 暂 存 前 后 的 两 个 版 本 如 果 我 们 的 环 境 ( 终 端 输 出 ) 看 起 来 如 下 : $ git add CONTRIBUTING.md $ echo '# test line' >> CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: CONTRIBUTING.md Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md 现 在 运 行 git diff 看 暂 存 前 后 的 变 化 : $ git diff diff --git a/contributing.md b/contributing.md index 643e24f..87f08c a/contributing.md ,3 at the ## Starter Projects See our [projects list]( +# test line

32 然 后 用 git diff --cached 查 看 已 经 暂 存 起 来 的 变 化 :(--staged 和 --cached 是 同 义 词 ) $ git diff --cached diff --git a/contributing.md b/contributing.md index 8ebb e24f a/contributing.md ,7 branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change -merged in. +merged in. Also, split your changes into comprehensive chunks if your patch is +longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's Git Diff 的 插 件 版 本 NOTE 在 本 书 中, 我 们 使 用 git diff 来 分 析 文 件 差 异 但 是, 如 果 你 喜 欢 通 过 图 形 化 的 方 式 或 其 它 格 式 输 出 方 式 的 话, 可 以 使 用 git difftool 命 令 来 用 Araxis,emerge 或 vimdiff 等 软 件 输 出 diff 分 析 结 果 使 用 git difftool --tool-help 命 令 来 看 你 的 系 统 支 持 哪 些 Git Diff 插 件 提 交 更 新 现 在 的 暂 存 区 域 已 经 准 备 妥 当 可 以 提 交 了 在 此 之 前, 请 一 定 要 确 认 还 有 什 么 修 改 过 的 或 新 建 的 文 件 还 没 有 git add 过, 否 则 提 交 的 时 候 不 会 记 录 这 些 还 没 暂 存 起 来 的 变 化 这 些 修 改 过 的 文 件 只 保 留 在 本 地 磁 盘 所 以, 每 次 准 备 提 交 前, 先 用 git status 看 下, 是 不 是 都 已 暂 存 起 来 了, 然 后 再 运 行 提 交 命 令 git commit: $ git commit 这 种 方 式 会 启 动 文 本 编 辑 器 以 便 输 入 本 次 提 交 的 说 明 ( 默 认 会 启 用 shell 的 环 境 变 量 $EDITOR 所 指 定 的 软 件, 一 般 都 是 vim 或 emacs 当 然 也 可 以 按 照 起 步 介 绍 的 方 式, 使 用 git config --global core.editor 命 令 设 定 你 喜 欢 的 编 辑 软 件 ) 编 辑 器 会 显 示 类 似 下 面 的 文 本 信 息 ( 本 例 选 用 Vim 的 屏 显 方 式 展 示 ):

33 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # new file: README # modified: CONTRIBUTING.md # ~ ~ ~ ".git/commit_editmsg" 9L, 283C 可 以 看 到, 默 认 的 提 交 消 息 包 含 最 后 一 次 运 行 git status 的 输 出, 放 在 注 释 行 里, 另 外 开 头 还 有 一 空 行, 供 你 输 入 提 交 说 明 你 完 全 可 以 去 掉 这 些 注 释 行, 不 过 留 着 也 没 关 系, 多 少 能 帮 你 回 想 起 这 次 更 新 的 内 容 有 哪 些 ( 如 果 想 要 更 详 细 的 对 修 改 了 哪 些 内 容 的 提 示, 可 以 用 -v 选 项, 这 会 将 你 所 做 的 改 变 的 diff 输 出 放 到 编 辑 器 中 从 而 使 你 知 道 本 次 提 交 具 体 做 了 哪 些 修 改 ) 退 出 编 辑 器 时,Git 会 丢 掉 注 释 行, 用 你 输 入 提 交 附 带 信 息 生 成 一 次 提 交 另 外, 你 也 可 以 在 commit 命 令 后 添 加 -m 选 项, 将 提 交 信 息 与 命 令 放 在 同 一 行, 如 下 所 示 : $ git commit -m "Story 182: Fix benchmarks for speed" [master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 2 insertions(+) create mode README 好, 现 在 你 已 经 创 建 了 第 一 个 提 交! 可 以 看 到, 提 交 后 它 会 告 诉 你, 当 前 是 在 哪 个 分 支 (master) 提 交 的, 本 次 提 交 的 完 整 SHA-1 校 验 和 是 什 么 (463dc4f), 以 及 在 本 次 提 交 中, 有 多 少 文 件 修 订 过, 多 少 行 添 加 和 删 改 过 请 记 住, 提 交 时 记 录 的 是 放 在 暂 存 区 域 的 快 照 任 何 还 未 暂 存 的 仍 然 保 持 已 修 改 状 态, 可 以 在 下 次 提 交 时 纳 入 版 本 管 理 每 一 次 运 行 提 交 操 作, 都 是 对 你 项 目 作 一 次 快 照, 以 后 可 以 回 到 这 个 状 态, 或 者 进 行 比 较 跳 过 使 用 暂 存 区 域 尽 管 使 用 暂 存 区 域 的 方 式 可 以 精 心 准 备 要 提 交 的 细 节, 但 有 时 候 这 么 做 略 显 繁 琐 Git 提 供 了 一 个 跳 过 使 用 暂 存 区 域 的 方 式, 只 要 在 提 交 的 时 候, 给 git commit 加 上 -a 选 项,Git 就 会 自 动 把 所 有 已 经 跟 踪 过 的 文 件 暂 存 起 来 一 并 提 交, 从 而 跳 过 git add 步 骤 :

34 $ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md no changes added to commit (use "git add" and/or "git commit -a") $ git commit -a -m 'added new benchmarks' [master 83e38c7] added new benchmarks 1 file changed, 5 insertions(+), 0 deletions(-) 看 到 了 吗? 提 交 之 前 不 再 需 要 git add 文 件 CONTRIBUTING.md 了 移 除 文 件 要 从 Git 中 移 除 某 个 文 件, 就 必 须 要 从 已 跟 踪 文 件 清 单 中 移 除 ( 确 切 地 说, 是 从 暂 存 区 域 移 除 ), 然 后 提 交 可 以 用 git rm 命 令 完 成 此 项 工 作, 并 连 带 从 工 作 目 录 中 删 除 指 定 的 文 件, 这 样 以 后 就 不 会 出 现 在 未 跟 踪 文 件 清 单 中 了 如 果 只 是 简 单 地 从 工 作 目 录 中 手 工 删 除 文 件, 运 行 git status 时 就 会 在 Changes not staged for commit 部 分 ( 也 就 是 未 暂 存 清 单 ) 看 到 : $ rm PROJECTS.md $ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: PROJECTS.md no changes added to commit (use "git add" and/or "git commit -a") 然 后 再 运 行 git rm 记 录 此 次 移 除 文 件 的 操 作 :

35 $ git rm PROJECTS.md rm 'PROJECTS.md' $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: PROJECTS.md 下 一 次 提 交 时, 该 文 件 就 不 再 纳 入 版 本 管 理 了 如 果 删 除 之 前 修 改 过 并 且 已 经 放 到 暂 存 区 域 的 话, 则 必 须 要 用 强 制 删 除 选 项 -f( 译 注 : 即 force 的 首 字 母 ) 这 是 一 种 安 全 特 性, 用 于 防 止 误 删 还 没 有 添 加 到 快 照 的 数 据, 这 样 的 数 据 不 能 被 Git 恢 复 另 外 一 种 情 况 是, 我 们 想 把 文 件 从 Git 仓 库 中 删 除 ( 亦 即 从 暂 存 区 域 移 除 ), 但 仍 然 希 望 保 留 在 当 前 工 作 目 录 中 换 句 话 说, 你 想 让 文 件 保 留 在 磁 盘, 但 是 并 不 想 让 Git 继 续 跟 踪 当 你 忘 记 添 加.gitignore 文 件, 不 小 心 把 一 个 很 大 的 日 志 文 件 或 一 堆.a 这 样 的 编 译 生 成 文 件 添 加 到 暂 存 区 时, 这 一 做 法 尤 其 有 用 为 达 到 这 一 目 的, 使 用 --cached 选 项 : $ git rm --cached README git rm 命 令 后 面 可 以 列 出 文 件 或 者 目 录 的 名 字, 也 可 以 使 用 glob 模 式 比 方 说 : $ git rm log/\*.log 注 意 到 星 号 * 之 前 的 反 斜 杠 \, 因 为 Git 有 它 自 己 的 文 件 模 式 扩 展 匹 配 方 式, 所 以 我 们 不 用 shell 来 帮 忙 展 开 此 命 令 删 除 log/ 目 录 下 扩 展 名 为.log 的 所 有 文 件 类 似 的 比 如 : $ git rm \*~ 该 命 令 为 删 除 以 ~ 结 尾 的 所 有 文 件 移 动 文 件 不 像 其 它 的 VCS 系 统,Git 并 不 显 式 跟 踪 文 件 移 动 操 作 如 果 在 Git 中 重 命 名 了 某 个 文 件, 仓 库 中 存 储 的 元 数 据 并 不 会 体 现 出 这 是 一 次 改 名 操 作 不 过 Git 非 常 聪 明, 它 会 推 断 出 究 竟 发 生 了 什 么, 至 于 具 体 是 如 何 做 到 的, 我 们 稍 后 再 谈 既 然 如 此, 当 你 看 到 Git 的 mv 命 令 时 一 定 会 困 惑 不 已 要 在 Git 中 对 文 件 改 名, 可 以 这 么 做 :

36 $ git mv file_from file_to 它 会 恰 如 预 期 般 正 常 工 作 实 际 上, 即 便 此 时 查 看 状 态 信 息, 也 会 明 白 无 误 地 看 到 关 于 重 命 名 操 作 的 说 明 : $ git mv README.md README $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README 其 实, 运 行 git mv 就 相 当 于 运 行 了 下 面 三 条 命 令 : $ mv README.md README $ git rm README.md $ git add README 如 此 分 开 操 作,Git 也 会 意 识 到 这 是 一 次 改 名, 所 以 不 管 何 种 方 式 结 果 都 一 样 两 者 唯 一 的 区 别 是,mv 是 一 条 命 令 而 另 一 种 方 式 需 要 三 条 命 令, 直 接 用 git mv 轻 便 得 多 不 过 有 时 候 用 其 他 工 具 批 处 理 改 名 的 话, 要 记 得 在 提 交 前 删 除 老 的 文 件 名, 再 添 加 新 的 文 件 名 查 看 提 交 历 史 在 提 交 了 若 干 更 新, 又 或 者 克 隆 了 某 个 项 目 之 后, 你 也 许 想 回 顾 下 提 交 历 史. 完 成 这 个 任 务 最 简 单 而 又 有 效 的 工 具 是 git log 命 令 接 下 来 的 例 子 会 用 我 专 门 用 于 演 示 的 simplegit 项 目, 运 行 下 面 的 命 令 获 取 该 项 目 源 代 码 : git clone 然 后 在 此 项 目 中 运 行 git log, 应 该 会 看 到 下 面 的 输 出 :

37 $ git log commit ca82a6dff817ec66f a Author: Scott Chacon Date: Mon Mar 17 21:52: changed the version number commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon Date: Sat Mar 15 16:40: removed unnecessary test commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon Date: Sat Mar 15 10:31: first commit 默 认 不 用 任 何 参 数 的 话,git log 会 按 提 交 时 间 列 出 所 有 的 更 新, 最 近 的 更 新 排 在 最 上 面 正 如 你 所 看 到 的, 这 个 命 令 会 列 出 每 个 提 交 的 SHA-1 校 验 和 作 者 的 名 字 和 电 子 邮 件 地 址 提 交 时 间 以 及 提 交 说 明 git log 有 许 多 选 项 可 以 帮 助 你 搜 寻 你 所 要 找 的 提 交, 接 下 来 我 们 介 绍 些 最 常 用 的 一 个 常 用 的 选 项 是 -p, 用 来 显 示 每 次 提 交 的 内 容 差 异 你 也 可 以 加 上 -2 来 仅 显 示 最 近 两 次 提 交 :

38 $ git log -p -2 commit ca82a6dff817ec66f a Author: Scott Chacon Date: Mon Mar 17 21:52: changed the version number diff --git a/rakefile b/rakefile index a874b73..8f a/rakefile +++ b/rakefile -5,7 +5,7 require 'rake/gempackagetask' spec = Gem::Specification.new do s s.platform = Gem::Platform::RUBY s.name = "simplegit" - s.version = "0.1.0" + s.version = "0.1.1" s.author = "Scott Chacon" s. = "schacon@ge .com" s.summary = "A simple gem for using Git in Ruby code." commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon <schacon@ge .com> Date: Sat Mar 15 16:40: removed unnecessary test diff --git a/lib/simplegit.rb b/lib/simplegit.rb index a0a60ae..47c a/lib/simplegit.rb ,8 class SimpleGit end end - -if $0 == FILE - git = SimpleGit.new - puts git.show -end \ No newline at end of file 该 选 项 除 了 显 示 基 本 信 息 之 外, 还 在 附 带 了 每 次 commit 的 变 化 当 进 行 代 码 审 查, 或 者 快 速 浏 览 某 个 搭 档 提 交 的 commit 所 带 来 的 变 化 的 时 候, 这 个 参 数 就 非 常 有 用 了 你 也 可 以 为 git log 附 带 一 系 列 的 总 结 性 选 项 比 如 说, 如 果 你 想 看 到 每 次 提 交 的 简 略 的 统 计 信 息, 你 可 以 使 用 --stat 选 项 :

39 $ git log --stat commit ca82a6dff817ec66f a Author: Scott Chacon Date: Mon Mar 17 21:52: changed the version number Rakefile file changed, 1 insertion(+), 1 deletion(-) commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon <schacon@ge .com> Date: Sat Mar 15 16:40: removed unnecessary test lib/simplegit.rb file changed, 5 deletions(-) commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon <schacon@ge .com> Date: Sat Mar 15 10:31: first commit README Rakefile lib/simplegit.rb files changed, 54 insertions(+) 正 如 你 所 看 到 的,--stat 选 项 在 每 次 提 交 的 下 面 列 出 额 所 有 被 修 改 过 的 文 件 有 多 少 文 件 被 修 改 了 以 及 被 修 改 过 的 文 件 的 哪 些 行 被 移 除 或 是 添 加 了 在 每 次 提 交 的 最 后 还 有 一 个 总 结 另 外 一 个 常 用 的 选 项 是 --pretty 这 个 选 项 可 以 指 定 使 用 不 同 于 默 认 格 式 的 方 式 展 示 提 交 历 史 这 个 选 项 有 一 些 内 建 的 子 选 项 供 你 使 用 比 如 用 oneline 将 每 个 提 交 放 在 一 行 显 示, 查 看 的 提 交 数 很 大 时 非 常 有 用 另 外 还 有 short,full 和 fuller 可 以 用, 展 示 的 信 息 或 多 或 少 有 些 不 同, 请 自 己 动 手 实 践 一 下 看 看 效 果 如 何 $ git log --pretty=oneline ca82a6dff817ec66f a changed the version number 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test a11bef06a3f659402fe7563abf99ad00de2209e6 first commit 但 最 有 意 思 的 是 format, 可 以 定 制 要 显 示 的 记 录 格 式 这 样 的 输 出 对 后 期 提 取 分 析 格 外 有 用 因 为 你 知 道 输 出 的 格 式 不 会 随 着 Git 的 更 新 而 发 生 改 变 :

40 $ git log --pretty=format:"%h - %an, %ar : %s" ca82a6d - Scott Chacon, 6 years ago : changed the version number 085bb3b - Scott Chacon, 6 years ago : removed unnecessary test a11bef0 - Scott Chacon, 6 years ago : first commit git log --pretty=format 常 用 的 选 项 列 出 了 常 用 的 格 式 占 位 符 写 法 及 其 代 表 的 意 义 Table 1. git log --pretty=format 常 用 的 选 项 选 项 说 明 %H 提 交 对 象 (commit) 的 完 整 哈 希 字 串 %h 提 交 对 象 的 简 短 哈 希 字 串 %T 树 对 象 (tree) 的 完 整 哈 希 字 串 %t 树 对 象 的 简 短 哈 希 字 串 %P 父 对 象 (parent) 的 完 整 哈 希 字 串 %p 父 对 象 的 简 短 哈 希 字 串 %an 作 者 (author) 的 名 字 %ae 作 者 的 电 子 邮 件 地 址 %ad 作 者 修 订 日 期 ( 可 以 用 --date= 选 项 定 制 格 式 ) %ar 作 者 修 订 日 期, 按 多 久 以 前 的 方 式 显 示 %cn 提 交 者 (committer) 的 名 字 %ce 提 交 者 的 电 子 邮 件 地 址 %cd 提 交 日 期 %cr 提 交 日 期, 按 多 久 以 前 的 方 式 显 示 %s 提 交 说 明 你 一 定 奇 怪 作 者 和 提 交 者 之 间 究 竟 有 何 差 别, 其 实 作 者 指 的 是 实 际 作 出 修 改 的 人, 提 交 者 指 的 是 最 后 将 此 工 作 成 果 提 交 到 仓 库 的 人 所 以, 当 你 为 某 个 项 目 发 布 补 丁, 然 后 某 个 核 心 成 员 将 你 的 补 丁 并 入 项 目 时, 你 就 是 作 者, 而 那 个 核 心 成 员 就 是 提 交 者 我 们 会 在 分 布 式 Git 再 详 细 介 绍 两 者 之 间 的 细 微 差 别 当 oneline 或 format 与 另 一 个 log 选 项 --graph 结 合 使 用 时 尤 其 有 用 这 个 选 项 添 加 了 一 些 ASCII 字 符 串 来 形 象 地 展 示 你 的 分 支 合 并 历 史 :

41 $ git log --pretty=format:"%h %s" --graph * 2d3acf9 ignore errors from SIGCHLD on trap * 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit \ * 420eac9 Added a method for getting the current branch. * 30e367c timeout code and tests * 5a09431 add timeout protection to grit * e1193f8 support for heads with slashes in them / * d6016bc require time for xmlschema * 11d191e Merge branch 'defunkt' into local 这 种 输 出 类 型 会 在 我 们 下 一 张 学 完 分 支 与 合 并 以 后 变 得 更 加 有 趣 以 上 只 是 简 单 介 绍 了 一 些 git log 命 令 支 持 的 选 项 git log 的 常 用 选 项 列 出 了 我 们 目 前 涉 及 到 的 和 没 涉 及 到 的 选 项, 已 经 它 们 是 如 何 影 响 log 命 令 的 输 出 的 : Table 2. git log 的 常 用 选 项 选 项 说 明 -p 按 补 丁 格 式 显 示 每 个 更 新 之 间 的 差 异 --stat 显 示 每 次 更 新 的 文 件 修 改 统 计 信 息 --shortstat 只 显 示 --stat 中 最 后 的 行 数 修 改 添 加 移 除 统 计 --name-only 仅 在 提 交 信 息 后 显 示 已 修 改 的 文 件 清 单 --name-status 显 示 新 增 修 改 删 除 的 文 件 清 单 --abbrev-commit 仅 显 示 SHA-1 的 前 几 个 字 符, 而 非 所 有 的 40 个 字 符 --relative-date 使 用 较 短 的 相 对 时 间 显 示 ( 比 如, 2 weeks ago ) --graph 显 示 ASCII 图 形 表 示 的 分 支 合 并 历 史 --pretty 使 用 其 他 格 式 显 示 历 史 提 交 信 息 可 用 的 选 项 包 括 oneline,short,full,fuller 和 format( 后 跟 指 定 格 式 ) 限 制 输 出 长 度 除 了 定 制 输 出 格 式 的 选 项 之 外,git log 还 有 许 多 非 常 实 用 的 限 制 输 出 长 度 的 选 项, 也 就 是 只 输 出 部 分 提 交 信 息 之 前 你 已 经 看 到 过 -2 了, 它 只 显 示 最 近 的 两 条 提 交, 实 际 上, 这 是 -<n> 选 项 的 写 法, 其 中 的 n 可 以 是 任 何 整 数, 表 示 仅 显 示 最 近 的 若 干 条 提 交 不 过 实 践 中 我 们 是 不 太 用 这 个 选 项 的,Git 在 输 出 所 有 提 交 时 会 自 动 调 用 分 页 程 序, 所 以 你 一 次 只 会 看 到 一 页 的 内 容 另 外 还 有 按 照 时 间 作 限 制 的 选 项, 比 如 --since 和 --until 也 很 有 用 例 如, 下 面 的 命 令 列 出 所 有 最 近 两 周 内 的 提 交 : $ git log --since=2.weeks

42 这 个 命 令 可 以 在 多 种 格 式 下 工 作, 比 如 说 具 体 的 某 一 天 " ", 或 者 是 相 对 地 多 久 以 前 "2 years 1 day 3 minutes ago" 还 可 以 给 出 若 干 搜 索 条 件, 列 出 符 合 的 提 交 用 --author 选 项 显 示 指 定 作 者 的 提 交, 用 --grep 选 项 搜 索 提 交 说 明 中 的 关 键 字 ( 请 注 意, 如 果 要 得 到 同 时 满 足 这 两 个 选 项 搜 索 条 件 的 提 交, 就 必 须 用 --all-match 选 项 否 则, 满 足 任 意 一 个 条 件 的 提 交 都 会 被 匹 配 出 来 ) 另 一 个 非 常 有 用 的 筛 选 选 项 是 -S, 可 以 列 出 那 些 添 加 或 移 除 了 某 些 字 符 串 的 提 交 比 如 说, 你 想 找 出 添 加 或 移 除 了 某 一 个 特 定 函 数 的 引 用 的 提 交, 你 可 以 这 样 使 用 : $ git log -Sfunction_name 最 后 一 个 很 实 用 的 git log 选 项 是 路 径 (path), 如 果 只 关 心 某 些 文 件 或 者 目 录 的 历 史 提 交, 可 以 在 git log 选 项 的 最 后 指 定 它 们 的 路 径 因 为 是 放 在 最 后 位 置 上 的 选 项, 所 以 用 两 个 短 划 线 (--) 隔 开 之 前 的 选 项 和 后 面 限 定 的 路 径 名 在 限 制 git log 输 出 的 选 项 中 列 出 了 常 用 的 选 项 Table 3. 限 制 git log 输 出 的 选 项 选 项 -(n) 说 明 仅 显 示 最 近 的 n 条 提 交 --since, --after 仅 显 示 指 定 时 间 之 后 的 提 交 --until, --before 仅 显 示 指 定 时 间 之 前 的 提 交 --author 仅 显 示 指 定 作 者 相 关 的 提 交 --committer 仅 显 示 指 定 提 交 者 相 关 的 提 交 --grep 仅 显 示 含 指 定 关 键 字 的 提 交 -S 仅 显 示 添 加 或 移 除 了 某 个 关 键 字 的 提 交 来 看 一 个 实 际 的 例 子, 如 果 要 查 看 Git 仓 库 中,2008 年 10 月 期 间,Junio Hamano 提 交 的 但 未 合 并 的 测 试 文 件, 可 以 用 下 面 的 查 询 命 令 : $ git log --pretty="%h - %s" --author=gitster --since=" " \ --before=" " --no-merges -- t/ 5610e3b - Fix testcase failure when extended attributes are in use acd3b9e - Enhance hold_lock_file_for_{update,append}() API f demonstrate breakage of detached checkout with symbolic link HEAD d1a43f2 - reset --hard/read-tree --reset -u: remove unmerged new paths 51a94af - Fix "checkout --track -b newbranch" on detached HEAD b0ad11e - pull: allow "git pull origin $something:$current_branch" into an unborn branch

43 在 近 条 提 交 中, 上 面 的 输 出 仅 列 出 了 符 合 条 件 的 6 条 记 录 撤 消 操 作 在 任 何 一 个 阶 段, 你 都 有 可 能 想 要 撤 消 某 些 操 作 这 里, 我 们 将 会 学 习 几 个 撤 消 你 所 做 修 改 的 基 本 工 具 注 意, 有 些 撤 消 操 作 是 不 可 逆 的 这 是 在 使 用 Git 的 过 程 中, 会 因 为 操 作 失 误 而 导 致 之 前 的 工 作 丢 失 的 少 有 的 几 个 地 方 之 一 有 时 候 我 们 提 交 完 了 才 发 现 漏 掉 了 几 个 文 件 没 有 添 加, 或 者 提 交 信 息 写 错 了 此 时, 可 以 运 行 带 有 --amend 选 项 的 提 交 命 令 尝 试 重 新 提 交 : $ git commit --amend 这 个 命 令 会 将 暂 存 区 中 的 文 件 提 交 如 果 自 上 次 提 交 以 来 你 还 未 做 任 何 修 改 ( 例 如, 在 上 次 提 交 后 马 上 执 行 了 此 命 令 ), 那 么 快 照 会 保 持 不 变, 而 你 所 修 改 的 只 是 提 交 信 息 文 本 编 辑 器 启 动 后, 可 以 看 到 之 前 的 提 交 信 息 编 辑 后 保 存 会 覆 盖 原 来 的 提 交 信 息 例 如, 你 提 交 后 发 现 忘 记 了 暂 存 某 些 需 要 的 修 改, 可 以 像 下 面 这 样 操 作 : $ git commit -m 'initial commit' $ git add forgotten_file $ git commit --amend 最 终 你 只 会 有 一 个 提 交 - 第 二 次 提 交 将 代 替 第 一 次 提 交 的 结 果 取 消 暂 存 的 文 件 接 下 来 的 两 个 小 节 演 示 如 何 操 作 暂 存 区 域 与 工 作 目 录 中 已 修 改 的 文 件 这 些 命 令 在 修 改 文 件 状 态 的 同 时, 也 会 提 示 如 何 撤 消 操 作 例 如, 你 已 经 修 改 了 两 个 文 件 并 且 想 要 将 它 们 作 为 两 次 独 立 的 修 改 提 交, 但 是 却 意 外 地 输 入 了 git add * 暂 存 了 它 们 两 个 如 何 只 取 消 暂 存 两 个 中 的 一 个 呢?git status 命 令 提 示 了 你 : $ git add * $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: modified: README.md -> README CONTRIBUTING.md 在 Changes to be committed 文 字 正 下 方, 提 示 使 用 git reset HEAD <file>... 来 取 消 暂 存 所

44 以, 我 们 可 以 这 样 来 取 消 暂 存 CONTRIBUTING.md 文 件 : $ git reset HEAD CONTRIBUTING.md Unstaged changes after reset: M CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md 这 个 命 令 有 点 儿 奇 怪, 但 是 起 作 用 了 CONTRIBUTING.md 文 件 已 经 是 修 改 未 暂 存 的 状 态 了 NOTE 虽 然 在 调 用 时 加 上 --hard 选 项 可 以 令 git reset 成 为 一 个 危 险 的 命 令 ( 译 注 : 可 能 导 致 工 作 目 录 中 所 有 当 前 进 度 丢 失!), 但 本 例 中 工 作 目 录 内 的 文 件 并 不 会 被 修 改 不 加 选 项 地 调 用 git reset 并 不 危 险 它 只 会 修 改 暂 存 区 域 到 目 前 为 止 这 个 神 奇 的 调 用 就 是 你 需 要 对 git reset 命 令 了 解 的 全 部 我 们 将 会 在 重 置 揭 密 中 了 解 reset 的 更 多 细 节 以 及 如 何 掌 握 它 做 一 些 真 正 有 趣 的 事 撤 消 对 文 件 的 修 改 如 果 你 并 不 想 保 留 对 CONTRIBUTING.md 文 件 的 修 改 怎 么 办? 你 该 如 何 方 便 地 撤 消 修 改 - 将 它 还 原 成 上 次 提 交 时 的 样 子 ( 或 者 刚 克 隆 完 的 样 子, 或 者 刚 把 它 放 入 工 作 目 录 时 的 样 子 )? 幸 运 的 是,git status 也 告 诉 了 你 应 该 如 何 做 在 最 后 一 个 例 子 中, 未 暂 存 区 域 是 这 样 : Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: CONTRIBUTING.md 它 非 常 清 楚 地 告 诉 了 你 如 何 撤 消 之 前 所 做 的 修 改 让 我 们 来 按 照 提 示 执 行 :

45 $ git checkout -- CONTRIBUTING.md $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README 可 以 看 到 那 些 修 改 已 经 被 撤 消 了 IMPORTANT 你 需 要 知 道 git checkout -- [file] 是 一 个 危 险 的 命 令, 这 很 重 要 你 对 那 个 文 件 做 的 任 何 修 改 都 会 消 失 - 你 只 是 拷 贝 了 另 一 个 文 件 来 覆 盖 它 除 非 你 确 实 清 楚 不 想 要 那 个 文 件 了, 否 则 不 要 使 用 这 个 命 令 如 果 你 仍 然 想 保 留 对 那 个 文 件 做 出 的 修 改, 但 是 现 在 仍 然 需 要 撤 消, 我 们 将 会 在 Git 分 支 介 绍 保 存 进 度 与 分 支 ; 这 些 通 常 是 更 好 的 做 法 记 住, 在 Git 中 任 何 已 提 交 的 东 西 几 乎 总 是 可 以 恢 复 的 甚 至 那 些 被 删 除 的 分 支 中 的 提 交 或 使 用 --amend 选 项 覆 盖 的 提 交 也 可 以 恢 复 ( 阅 读 数 据 恢 复 了 解 数 据 恢 复 ) 然 而, 任 何 你 未 提 交 的 东 西 丢 失 后 很 可 能 再 也 找 不 到 了 远 程 仓 库 的 使 用 为 了 能 在 任 意 Git 项 目 上 协 作, 你 需 要 知 道 如 何 管 理 自 己 的 远 程 仓 库 远 程 仓 库 是 指 托 管 在 因 特 网 或 其 他 网 络 中 的 你 的 项 目 的 版 本 库 你 可 以 有 好 几 个 远 程 仓 库, 通 常 有 些 仓 库 对 你 只 读, 有 些 则 可 以 读 写 与 他 人 协 作 涉 及 管 理 远 程 仓 库 以 及 根 据 需 要 推 送 或 拉 取 数 据 管 理 远 程 仓 库 包 括 了 解 如 何 添 加 远 程 仓 库 移 除 无 效 的 远 程 仓 库 管 理 不 同 的 远 程 分 支 并 定 义 它 们 是 否 被 跟 踪 等 等 在 本 节 中, 我 们 将 介 绍 一 部 分 远 程 管 理 的 技 能 查 看 远 程 仓 库 如 果 想 查 看 你 已 经 配 置 的 远 程 仓 库 服 务 器, 可 以 运 行 git remote 命 令 它 会 列 出 你 指 定 的 每 一 个 远 程 服 务 器 的 简 写 如 果 你 已 经 克 隆 了 自 己 的 仓 库, 那 么 至 少 应 该 能 看 到 origin - 这 是 Git 给 你 克 隆 的 仓 库 服 务 器 的 默 认 名 字 :

46 $ git clone Cloning into 'ticgit'... remote: Reusing existing pack: 1857, done. remote: Total 1857 (delta 0), reused 0 (delta 0) Receiving objects: 100% (1857/1857), KiB KiB/s, done. Resolving deltas: 100% (772/772), done. Checking connectivity... done. $ cd ticgit $ git remote origin 你 也 可 以 指 定 选 项 -v, 会 显 示 需 要 读 写 远 程 仓 库 使 用 的 Git 保 存 的 简 写 与 其 对 应 的 URL $ git remote -v origin (fetch) origin (push) 如 果 你 的 远 程 仓 库 不 止 一 个, 该 命 令 会 将 它 们 全 部 列 出 例 如, 与 几 个 协 作 者 合 作 的, 拥 有 多 个 远 程 仓 库 的 仓 库 看 起 来 像 下 面 这 样 : $ cd grit $ git remote -v bakkdoor (fetch) bakkdoor (push) cho45 (fetch) cho45 (push) defunkt (fetch) defunkt (push) koke git://github.com/koke/grit.git (fetch) koke git://github.com/koke/grit.git (push) origin git@github.com:mojombo/grit.git (fetch) origin git@github.com:mojombo/grit.git (push) 这 样 我 们 可 以 轻 松 拉 取 其 中 任 何 一 个 用 户 的 贡 献 此 外, 我 们 大 概 还 会 有 某 些 远 程 仓 库 的 推 送 权 限, 虽 然 我 们 目 前 还 不 会 在 此 介 绍 注 意 这 些 远 程 仓 库 使 用 了 不 同 的 协 议 ; 我 们 将 会 在 在 服 务 器 上 搭 建 Git 中 了 解 关 于 它 们 的 更 多 信 息 添 加 远 程 仓 库 我 在 之 前 的 章 节 中 已 经 提 到 并 展 示 了 如 何 添 加 远 程 仓 库 的 示 例, 不 过 这 里 将 告 诉 你 如 何 明 确 地 做 到 这 一 点 运 行 git remote add <shortname> <url> 添 加 一 个 新 的 远 程 Git 仓 库, 同 时 指 定 一 个 你 可 以 轻 松 引 用 的 简 写 :

47 $ git remote origin $ git remote add pb $ git remote -v origin (fetch) origin (push) pb (fetch) pb (push) 现 在 你 可 以 在 命 令 行 中 使 用 字 符 串 pb 来 代 替 整 个 URL 例 如, 如 果 你 想 拉 取 Paul 的 仓 库 中 有 但 你 没 有 的 信 息, 可 以 运 行 git fetch pb: $ git fetch pb remote: Counting objects: 43, done. remote: Compressing objects: 100% (36/36), done. remote: Total 43 (delta 10), reused 31 (delta 5) Unpacking objects: 100% (43/43), done. From * [new branch] master -> pb/master * [new branch] ticgit -> pb/ticgit 现 在 Paul 的 master 分 支 可 以 在 本 地 通 过 pb/master 访 问 到 - 你 可 以 将 它 合 并 到 自 己 的 某 个 分 支 中, 或 者 如 果 你 想 要 查 看 它 的 话, 可 以 检 出 一 个 指 向 该 点 的 本 地 分 支 ( 我 们 将 会 在 Git 分 支 中 详 细 介 绍 什 么 是 分 支 以 及 如 何 使 用 分 支 ) 从 远 程 仓 库 中 抓 取 与 拉 取 就 如 刚 才 所 见, 从 远 程 仓 库 中 获 得 数 据, 可 以 执 行 : $ git fetch [remote-name] 这 个 命 令 会 访 问 远 程 仓 库, 从 中 拉 取 所 有 你 还 没 有 的 数 据 执 行 完 成 后, 你 将 会 拥 有 那 个 远 程 仓 库 中 所 有 分 支 的 引 用, 可 以 随 时 合 并 或 查 看 如 果 你 使 用 clone 命 令 克 隆 了 一 个 仓 库, 命 令 会 自 动 将 其 添 加 为 远 程 仓 库 并 默 认 以 origin 为 简 写 所 以,git fetch origin 会 抓 取 克 隆 ( 或 上 一 次 抓 取 ) 后 新 推 送 的 所 有 工 作 必 须 注 意 git fetch 命 令 会 将 数 据 拉 取 到 你 的 本 地 仓 库 - 它 并 不 会 自 动 合 并 或 修 改 你 当 前 的 工 作 当 准 备 好 时 你 必 须 手 动 将 其 合 并 入 你 的 工 作 如 果 你 有 一 个 分 支 设 置 为 跟 踪 一 个 远 程 分 支 ( 阅 读 下 一 节 与 Git 分 支 了 解 更 多 信 息 ), 可 以 使 用 git pull 命 令 来 自 动 的 抓 取 然 后 合 并 远 程 分 支 到 当 前 分 支 这 对 你 来 说 可 能 是 一 个 更 简 单 或 更 舒 服 的 工 作 流 程 ; 默 认 情 况 下,git clone 命 令 会 自 动 设 置 本 地 master 分 支 跟 踪 克 隆 的 远 程 仓 库 的 master 分 支 ( 或 不 管 是 什 么 名 字 的 默 认 分 支 ) 运 行 git pull 通 常 会 从 最 初 克 隆 的 服 务 器 上 抓 取 数 据 并 自 动 尝 试 合 并 到 当 前 所 在 的 分 支

48 推 送 到 远 程 仓 库 当 你 想 分 享 你 的 项 目 时, 必 须 将 其 推 送 到 上 游 这 个 命 令 很 简 单 :git push [remote-name] [branchname] 当 你 想 要 将 master 分 支 推 送 到 origin 服 务 器 时 ( 再 次 说 明, 克 隆 时 通 常 会 自 动 帮 你 设 置 好 那 两 个 名 字 ), 那 么 运 行 这 个 命 令 就 可 以 将 你 所 做 的 备 份 到 服 务 器 : $ git push origin master 只 有 当 你 有 所 克 隆 服 务 器 的 写 入 权 限, 并 且 之 前 没 有 人 推 送 过 时, 这 条 命 令 才 能 生 效 当 你 和 其 他 人 在 同 一 时 间 克 隆, 他 们 先 推 送 到 上 游 然 后 你 再 推 送 到 上 游, 你 的 推 送 就 会 毫 无 疑 问 地 被 拒 绝 你 必 须 先 将 他 们 的 工 作 拉 取 下 来 并 将 其 合 并 进 你 的 工 作 后 才 能 推 送 阅 读 Git 分 支 了 解 如 何 推 送 到 远 程 仓 库 服 务 器 的 详 细 信 息 查 看 远 程 仓 库 如 果 想 要 查 看 某 一 个 远 程 仓 库 的 更 多 信 息, 可 以 使 用 git remote show [remote-name] 命 令 如 果 想 以 一 个 特 定 的 缩 写 名 运 行 这 个 命 令, 例 如 origin, 会 得 到 像 下 面 类 似 的 信 息 : $ git remote show origin * remote origin Fetch URL: Push URL: HEAD branch: master Remote branches: master tracked dev-branch tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date) 它 同 样 会 列 出 远 程 仓 库 的 URL 与 跟 踪 分 支 的 信 息 这 些 信 息 非 常 有 用, 它 告 诉 你 正 处 于 master 分 支, 并 且 如 果 运 行 git pull, 就 会 抓 取 所 有 的 远 程 引 用, 然 后 将 远 程 master 分 支 合 并 到 本 地 master 分 支 它 也 会 列 出 拉 取 到 的 所 有 远 程 引 用 这 是 一 个 经 常 遇 到 的 简 单 例 子 如 果 你 是 Git 的 重 度 使 用 者, 那 么 还 可 以 通 过 git remote show 看 到 更 多 的 信 息

49 $ git remote show origin * remote origin URL: Fetch URL: Push URL: HEAD branch: master Remote branches: master tracked dev-branch tracked markdown-strip tracked issue-43 new (next fetch will store in remotes/origin) issue-45 new (next fetch will store in remotes/origin) refs/remotes/origin/issue-11 stale (use 'git remote prune' to remove) Local branches configured for 'git pull': dev-branch merges with remote dev-branch master merges with remote master Local refs configured for 'git push': dev-branch pushes to dev-branch (up to date) markdown-strip pushes to markdown-strip (up to date) master pushes to master (up to date) 这 个 命 令 列 出 了 当 你 在 特 定 的 分 支 上 执 行 git push 会 自 动 地 推 送 到 哪 一 个 远 程 分 支 它 也 同 样 地 列 出 了 哪 些 远 程 分 支 不 在 你 的 本 地, 哪 些 远 程 分 支 已 经 从 服 务 器 上 移 除 了, 还 有 当 你 执 行 git pull 时 哪 些 分 支 会 自 动 合 并 远 程 仓 库 的 移 除 与 重 命 名 如 果 想 要 重 命 名 引 用 的 名 字 可 以 运 行 git remote rename 去 修 改 一 个 远 程 仓 库 的 简 写 名 例 如, 想 要 将 pb 重 命 名 为 paul, 可 以 用 git remote rename 这 样 做 : $ git remote rename pb paul $ git remote origin paul 值 得 注 意 的 是 这 同 样 也 会 修 改 你 的 远 程 分 支 名 字 那 些 过 去 引 用 pb/master 的 现 在 会 引 用 paul/master 如 果 因 为 一 些 原 因 想 要 移 除 一 个 远 程 仓 库 - 你 已 经 从 服 务 器 上 搬 走 了 或 不 再 想 使 用 某 一 个 特 定 的 镜 像 了, 又 或 者 某 一 个 贡 献 者 不 再 贡 献 了 - 可 以 使 用 git remote rm :

50 $ git remote rm paul $ git remote origin 打 标 签 像 其 他 版 本 控 制 系 统 (VCS) 一 样,Git 可 以 给 历 史 中 的 某 一 个 提 交 打 上 标 签, 以 示 重 要 比 较 有 代 表 性 的 是 人 们 会 使 用 这 个 功 能 来 标 记 发 布 结 点 (v1.0 等 等 ) 在 本 节 中, 你 将 会 学 习 如 何 列 出 已 有 的 标 签 如 何 创 建 新 标 签 以 及 不 同 类 型 的 标 签 分 别 是 什 么 列 出 标 签 在 Git 中 列 出 已 有 的 标 签 是 非 常 简 单 直 观 的 只 需 要 输 入 git tag: $ git tag v0.1 v1.3 这 个 命 令 以 字 母 顺 序 列 出 标 签 ; 但 是 它 们 出 现 的 顺 序 并 不 重 要 你 也 可 以 使 用 特 定 的 模 式 查 找 标 签 例 如,Git 自 身 的 源 代 码 仓 库 包 含 标 签 的 数 量 超 过 500 个 如 果 只 对 系 列 感 兴 趣, 可 以 运 行 : $ git tag -l 'v1.8.5*' v1.8.5 v1.8.5-rc0 v1.8.5-rc1 v1.8.5-rc2 v1.8.5-rc3 v v v v v 创 建 标 签 Git 使 用 两 种 主 要 类 型 的 标 签 : 轻 量 标 签 (lightweight) 与 附 注 标 签 (annotated) 一 个 轻 量 标 签 很 像 一 个 不 会 改 变 的 分 支 - 它 只 是 一 个 特 定 提 交 的 引 用 然 而, 附 注 标 签 是 存 储 在 Git 数 据 库 中 的 一 个 完 整 对 象 它 们 是 可 以 被 校 验 的 ; 其 中 包 含 打 标 签 者 的 名 字 电 子

51 邮 件 地 址 日 期 时 间 ; 还 有 一 个 标 签 信 息 ; 并 且 可 以 使 用 GNU Privacy Guard (GPG) 签 名 与 验 证 通 常 建 议 创 建 附 注 标 签, 这 样 你 可 以 拥 有 以 上 所 有 信 息 ; 但 是 如 果 你 只 是 想 用 一 个 临 时 的 标 签, 或 者 因 为 某 些 原 因 不 想 要 保 存 那 些 信 息, 轻 量 标 签 也 是 可 用 的 附 注 标 签 在 Git 中 创 建 一 个 附 注 标 签 是 很 简 单 的 最 简 单 的 方 式 是 当 你 在 运 行 tag 命 令 时 指 定 -a 选 项 : $ git tag -a v1.4 -m 'my version 1.4' $ git tag v0.1 v1.3 v1.4 -m 选 项 指 定 了 一 条 将 会 存 储 在 标 签 中 的 信 息 如 果 没 有 为 附 注 标 签 指 定 一 条 信 息,Git 会 运 行 编 辑 器 要 求 你 输 入 信 息 通 过 使 用 git show 命 令 可 以 看 到 标 签 信 息 与 对 应 的 提 交 信 息 : $ git show v1.4 tag v1.4 Tagger: Ben Straub <ben@straub.cc> Date: Sat May 3 20:19: my version 1.4 commit ca82a6dff817ec66f a Author: Scott Chacon <schacon@ge .com> Date: Mon Mar 17 21:52: changed the version number 输 出 显 示 了 打 标 签 者 的 信 息 打 标 签 的 日 期 时 间 附 注 信 息, 然 后 显 示 具 体 的 提 交 信 息 轻 量 标 签 另 一 种 给 提 交 打 标 签 的 方 式 是 使 用 轻 量 标 签 轻 量 标 签 本 质 上 是 将 提 交 校 验 和 存 储 到 一 个 文 件 中 - 没 有 保 存 任 何 其 他 信 息 创 建 轻 量 标 签, 不 需 要 使 用 -a -s 或 -m 选 项, 只 需 要 提 供 标 签 名 字 :

52 $ git tag v1.4-lw $ git tag v0.1 v1.3 v1.4 v1.4-lw v1.5 这 时, 如 果 在 标 签 上 运 行 git show, 你 不 会 看 到 额 外 的 标 签 信 息 命 令 只 会 显 示 出 提 交 信 息 : $ git show v1.4-lw commit ca82a6dff817ec66f a Author: Scott Chacon <schacon@ge .com> Date: Mon Mar 17 21:52: changed the version number 后 期 打 标 签 你 也 可 以 对 过 去 的 提 交 打 标 签 假 设 提 交 历 史 是 这 样 的 : $ git log --pretty=oneline b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment' a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support 0d52aaab da7686c15f77a3d64d one more thing 6d52a271eda dd79daabbc4d9b6008e Merge branch 'experiment' 0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function 4682c bdd616e23b64b0857d832627b added a todo file 166ae0c4d3f420721acbb115cc33848dfcc2121a started write support 9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile 964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo 8a5cbc430f1a9c3d00faaeffd a updated readme 现 在, 假 设 在 v1.2 时 你 忘 记 给 项 目 打 标 签, 也 就 是 在 updated rakefile 提 交 你 可 以 在 之 后 补 上 标 签 要 在 那 个 提 交 上 打 标 签, 你 需 要 在 命 令 的 末 尾 指 定 提 交 的 校 验 和 ( 或 部 分 校 验 和 ): $ git tag -a v1.2 9fceb02 可 以 看 到 你 已 经 在 那 次 提 交 上 打 上 标 签 了 :

53 $ git tag v0.1 v1.2 v1.3 v1.4 v1.4-lw v1.5 $ git show v1.2 tag v1.2 Tagger: Scott Chacon <schacon@ge .com> Date: Mon Feb 9 15:32: version 1.2 commit 9fceb02d0ae598e95dc970b74767f19372d61af8 Author: Magnus Chacon <mchacon@ge .com> Date: Sun Apr 27 20:43: updated rakefile 共 享 标 签 默 认 情 况 下,git push 命 令 并 不 会 传 送 标 签 到 远 程 仓 库 服 务 器 上 在 创 建 完 标 签 后 你 必 须 显 式 地 推 送 标 签 到 共 享 服 务 器 上 这 个 过 程 就 像 共 享 远 程 分 支 一 样 - 你 可 以 运 行 git push origin [tagname] $ git push origin v1.5 Counting objects: 14, done. Delta compression using up to 8 threads. Compressing objects: 100% (12/12), done. Writing objects: 100% (14/14), 2.05 KiB 0 bytes/s, done. Total 14 (delta 3), reused 0 (delta 0) To git@github.com:schacon/simplegit.git * [new tag] v1.5 -> v1.5 如 果 想 要 一 次 性 推 送 很 多 标 签, 也 可 以 使 用 带 有 --tags 选 项 的 git push 命 令 这 将 会 把 所 有 不 在 远 程 仓 库 服 务 器 上 的 标 签 全 部 传 送 到 那 里

54 $ git push origin --tags Counting objects: 1, done. Writing objects: 100% (1/1), 160 bytes 0 bytes/s, done. Total 1 (delta 0), reused 0 (delta 0) To git@github.com:schacon/simplegit.git * [new tag] v1.4 -> v1.4 * [new tag] v1.4-lw -> v1.4-lw 现 在, 当 其 他 人 从 仓 库 中 克 隆 或 拉 取, 他 们 也 能 得 到 你 的 那 些 标 签 检 出 标 签 在 Git 中 你 并 不 能 真 的 检 出 一 个 标 签, 因 为 它 们 并 不 能 像 分 支 一 样 来 回 移 动 如 果 你 想 要 工 作 目 录 与 仓 库 中 特 定 的 标 签 版 本 完 全 一 样, 可 以 使 用 git checkout -b [branchname] [tagname] 在 特 定 的 标 签 上 创 建 一 个 新 分 支 : $ git checkout -b version2 v2.0.0 Switched to a new branch 'version2' 当 然, 如 果 在 这 之 后 又 进 行 了 一 次 提 交,version2 分 支 会 因 为 改 动 向 前 移 动 了, 那 么 version2 分 支 就 会 和 v2.0.0 标 签 稍 微 有 些 不 同, 这 时 就 应 该 当 心 了 Git 别 名 在 我 们 结 束 本 章 Git 基 础 之 前, 正 好 有 一 个 小 技 巧 可 以 使 你 的 Git 体 验 更 简 单 容 易 熟 悉 : 别 名 我 们 不 会 在 之 后 的 章 节 中 引 用 到 或 假 定 你 使 用 过 它 们, 但 是 你 大 概 应 该 知 道 如 何 使 用 它 们 Git 并 不 会 在 你 输 入 部 分 命 令 时 自 动 推 断 出 你 想 要 的 命 令 如 果 不 想 每 次 都 输 入 完 整 的 Git 命 令, 可 以 通 过 git config 文 件 来 轻 松 地 为 每 一 个 命 令 设 置 一 个 别 名 这 里 有 一 些 例 子 你 可 以 试 试 : $ git config --global alias.co checkout $ git config --global alias.br branch $ git config --global alias.ci commit $ git config --global alias.st status 这 意 味 着, 当 要 输 入 git commit` 时, 只 需 要 输 入 `git ci 随 着 你 继 续 不 断 地 使 用 Git, 可 能 也 会 经 常 使 用 其 他 命 令, 所 以 创 建 别 名 时 不 要 犹 豫 在 创 建 你 认 为 应 该 存 在 的 命 令 时 这 个 技 术 会 很 有 用 例 如, 为 了 解 决 取 消 暂 存 文 件 的 易 用 性 问 题, 可 以 向 Git 中 添 加 你 自 己 的 取 消 暂 存 别 名 :

55 $ git config --global alias.unstage 'reset HEAD --' 这 会 使 下 面 的 两 个 命 令 等 价 : $ git unstage filea $ git reset HEAD -- filea 这 样 看 起 来 更 清 楚 一 些 通 常 也 会 添 加 一 个 last 命 令, 像 这 样 : $ git config --global alias.last 'log -1 HEAD' 这 样, 可 以 轻 松 地 看 到 最 后 一 次 提 交 : $ git last commit 66938dae3329c7aebe598c2246a8e6af90d04646 Author: Josh Goebel <dreamer3@example.com> Date: Tue Aug 26 19:48: test for current head Signed-off-by: Scott Chacon <schacon@example.com> 可 以 看 出,Git 只 是 简 单 地 将 别 名 替 换 为 对 应 的 命 令 然 而, 你 可 能 想 要 执 行 外 部 命 令, 而 不 是 一 个 Git 子 命 令 如 果 是 那 样 的 话, 可 以 在 命 令 前 面 加 入! 符 号 如 果 你 自 己 要 写 一 些 与 Git 仓 库 协 作 的 工 具 的 话, 那 会 很 有 用 我 们 现 在 演 示 将 git visual 定 义 为 gitk 的 别 名 : $ git config --global alias.visual '!gitk' 总 结 现 在, 你 可 以 完 成 所 有 基 本 的 Git 本 地 操 作 - 创 建 或 者 克 隆 一 个 仓 库 做 更 改 暂 存 并 提 交 这 些 更 改 浏 览 你 的 仓 库 从 创 建 到 现 在 的 所 有 更 改 的 历 史 下 一 步, 本 书 将 介 绍 Git 的 杀 手 级 特 性 : 分 支 模 型

56 Git 分 支 几 乎 所 有 的 版 本 控 制 系 统 都 以 某 种 形 式 支 持 分 支 使 用 分 支 意 味 着 你 可 以 把 你 的 工 作 从 开 发 主 线 上 分 离 开 来, 以 免 影 响 开 发 主 线 在 很 多 版 本 控 制 系 统 中, 这 是 一 个 略 微 低 效 的 过 程 常 常 需 要 完 全 创 建 一 个 源 代 码 目 录 的 副 本 对 于 大 项 目 来 说, 这 样 的 过 程 会 耗 费 很 多 时 间 有 人 把 Git 的 分 支 模 型 称 为 它 的 ` 必 杀 技 特 性 ', 也 正 因 为 这 一 特 性, 使 得 Git 从 众 多 版 本 控 制 系 统 中 脱 颖 而 出 为 何 Git 的 分 支 模 型 如 此 出 众 呢?Git 处 理 分 支 的 方 式 可 谓 是 难 以 置 信 的 轻 量, 创 建 新 分 支 这 一 操 作 几 乎 能 在 瞬 间 完 成, 并 且 在 不 同 分 支 之 间 的 切 换 操 作 也 是 一 样 便 捷 与 许 多 其 它 版 本 控 制 系 统 不 同,Git 鼓 励 在 工 作 流 程 中 频 繁 地 使 用 分 支 与 合 并, 哪 怕 一 天 之 内 进 行 许 多 次 理 解 和 精 通 这 一 特 性, 你 便 会 意 识 到 Git 是 如 此 的 强 大 而 又 独 特, 并 且 从 此 真 正 改 变 你 的 开 发 方 式 分 支 简 介 为 了 真 正 理 解 Git 处 理 分 支 的 方 式, 我 们 需 要 回 顾 一 下 Git 是 如 何 保 存 数 据 的 或 许 你 还 记 得 起 步 的 内 容,Git 保 存 的 不 是 文 件 的 变 化 或 者 差 异, 而 是 一 系 列 不 同 时 刻 的 文 件 快 照 在 进 行 提 交 操 作 时,Git 会 保 存 一 个 提 交 对 象 (commit object) 知 道 了 Git 保 存 数 据 的 方 式, 我 们 可 以 很 自 然 的 想 到 该 提 交 对 象 会 包 含 一 个 指 向 暂 存 内 容 快 照 的 指 针 但 不 仅 仅 是 这 样, 该 提 交 对 象 还 包 含 了 作 者 的 姓 名 和 邮 箱 提 交 时 输 入 的 信 息 以 及 指 向 它 的 父 对 象 的 指 针 首 次 提 交 产 生 的 提 交 对 象 没 有 父 对 象, 普 通 提 交 操 作 产 生 的 提 交 对 象 有 一 个 父 对 象, 而 由 多 个 分 支 合 并 产 生 的 提 交 对 象 有 多 个 父 对 象, 为 了 说 得 更 加 形 象, 我 们 假 设 现 在 有 一 个 工 作 目 录, 里 面 包 含 了 三 个 将 要 被 暂 存 和 提 交 的 文 件 暂 存 操 作 会 为 每 一 个 文 件 计 算 校 验 和 ( 使 用 我 们 在 起 步 中 提 到 的 SHA-1 哈 希 算 法 ), 然 后 会 把 当 前 版 本 的 文 件 快 照 保 存 到 Git 仓 库 中 (Git 使 用 blob 对 象 来 保 存 它 们 ), 最 终 将 校 验 和 加 入 到 暂 存 区 域 等 待 提 交 : $ git add README test.rb LICENSE $ git commit -m 'The initial commit of my project' 当 使 用 git commit 进 行 提 交 操 作 时,Git 会 先 计 算 每 一 个 子 目 录 ( 本 例 中 只 有 项 目 根 目 录 ) 的 校 验 和, 然 后 在 Git 仓 库 中 这 些 校 验 和 保 存 为 树 对 象 随 后,Git 便 会 创 建 一 个 提 交 对 象, 它 除 了 包 含 上 面 提 到 的 那 些 信 息 外, 还 包 含 指 向 这 个 树 对 象 ( 项 目 根 目 录 ) 的 指 针 如 此 一 来,Git 就 可 以 在 需 要 的 时 候 重 现 此 次 保 存 的 快 照 现 在,Git 仓 库 中 有 五 个 对 象 : 三 个 blob 对 象 ( 保 存 着 文 件 快 照 ) 一 个 树 对 象 ( 记 录 着 目 录 结 构 和 blob 对 象 索 引 ) 以 及 一 个 提 交 对 象 ( 包 含 着 指 向 前 述 树 对 象 的 指 针 和 所 有 提 交 信 息 )

57 Figure 9. 首 次 提 交 对 象 及 其 树 结 构 做 些 修 改 后 再 次 提 交, 那 么 这 次 产 生 的 提 交 对 象 会 包 含 一 个 指 向 上 次 提 交 对 象 ( 父 对 象 ) 的 指 针 Figure 10. 提 交 对 象 及 其 父 对 象 Git 的 分 支, 其 实 本 质 上 仅 仅 是 指 向 提 交 对 象 的 可 变 指 针 Git 的 默 认 分 支 名 字 是 master 在 多 次 提 交 操 作 之 后, 你 其 实 已 经 有 一 个 指 向 最 后 那 个 提 交 对 象 的 master 分 支 它 会 在 每 次 的 提 交 操 作 中 自 动 向 前 移 动 NOTE Git 的 master 分 支 并 不 是 一 个 特 殊 分 支 它 就 跟 其 它 分 支 完 全 没 有 区 别 之 所 以 几 乎 每 一 个 仓 库 都 有 master 分 支, 是 因 为 git init 命 令 默 认 创 建 它, 并 且 大 多 数 人 都 懒 得 去 改 动 它

58 Figure 11. 分 支 及 其 提 交 历 史 分 支 创 建 Git 是 怎 么 创 建 新 分 支 的 呢? 很 简 单, 它 只 是 为 你 创 建 了 一 个 可 以 移 动 的 新 的 指 针 比 如, 创 建 一 个 testing 分 支, 你 需 要 使 用 git branch 命 令 : $ git branch testing 这 会 在 当 前 所 在 的 提 交 对 象 上 创 建 一 个 指 针 Figure 12. 两 个 指 向 相 同 提 交 历 史 的 分 支

59 那 么,Git 又 是 怎 么 知 道 当 前 在 哪 一 个 分 支 上 呢? 也 很 简 单, 它 有 一 个 名 为 HEAD 的 特 殊 指 针 请 注 意 它 和 许 多 其 它 版 本 控 制 系 统 ( 如 Subversion 或 CVS) 里 的 HEAD 概 念 完 全 不 同 在 Git 中, 它 是 一 个 指 针, 指 向 当 前 所 在 的 本 地 分 支 ( 译 注 : 将 HEAD 想 象 为 当 前 分 支 的 别 名 ) 在 本 例 中, 你 仍 然 在 master 分 支 上 因 为 git branch 命 令 仅 仅 创 建 一 个 新 分 支, 并 不 会 自 动 切 换 到 新 分 支 中 去 Figure 13. HEAD 指 向 当 前 所 在 的 分 支 你 可 以 简 单 地 使 用 git log 命 令 查 看 各 个 分 支 当 前 所 指 的 对 象 提 供 这 一 功 能 的 参 数 是 --decorate $ git log --oneline --decorate f30ab (HEAD, master, testing) add feature #32 - ability to add new 34ac2 fixed bug # stack overflow under certain conditions 98ca9 initial commit of my project 正 如 你 所 见, 当 前 master 和 testing 分 支 均 指 向 校 验 和 以 f30ab 开 头 的 提 交 对 象 分 支 切 换 要 切 换 到 一 个 已 存 在 的 分 支, 你 需 要 使 用 git checkout 命 令 我 们 现 在 切 换 到 新 创 建 的 testing 分 支 去 : $ git checkout testing 这 样 HEAD 就 指 向 testing 分 支 了

60 Figure 14. HEAD 指 向 当 前 所 在 的 分 支 那 么, 这 样 的 实 现 方 式 会 给 我 们 带 来 什 么 好 处 呢? 现 在 不 妨 再 提 交 一 次 : $ vim test.rb $ git commit -a -m 'made a change' Figure 15. HEAD 分 支 随 着 提 交 操 作 自 动 向 前 移 动 如 图 所 示, 你 的 testing 分 支 向 前 移 动 了, 但 是 master 分 支 却 没 有, 它 仍 然 指 向 运 行 git checkout 时 所 指 的 对 象 这 就 有 意 思 了, 现 在 我 们 切 换 回 master 分 支 看 看 :

61 $ git checkout master Figure 16. 检 出 时 HEAD 随 之 移 动 这 条 命 令 做 了 两 件 事 一 是 使 HEAD 指 回 master 分 支, 二 是 将 工 作 目 录 恢 复 成 master 分 支 所 指 向 的 快 照 内 容 也 就 是 说, 你 现 在 做 修 改 的 话, 项 目 将 始 于 一 个 较 旧 的 版 本 本 质 上 来 讲, 这 就 是 忽 略 testing 分 支 所 做 的 修 改, 以 便 于 向 另 一 个 方 向 进 行 开 发 分 支 切 换 会 改 变 你 工 作 目 录 中 的 文 件 NOTE 在 切 换 分 支 时, 一 定 要 注 意 你 工 作 目 录 里 的 文 件 会 被 改 变 如 果 是 切 换 到 一 个 较 旧 的 分 支, 你 的 工 作 目 录 会 恢 复 到 该 分 支 最 后 一 次 提 交 时 的 样 子 如 果 Git 不 能 干 净 利 落 地 完 成 这 个 任 务, 它 将 禁 止 切 换 分 支 我 们 不 妨 再 稍 微 做 些 修 改 并 提 交 : $ vim test.rb $ git commit -a -m 'made other changes' 现 在, 这 个 项 目 的 提 交 历 史 已 经 产 生 了 分 叉 ( 参 见 项 目 分 叉 历 史 ) 因 为 刚 才 你 创 建 了 一 个 新 分 支, 并 切 换 过 去 进 行 了 一 些 工 作, 随 后 又 切 换 回 master 分 支 进 行 了 另 外 一 些 工 作 上 述 两 次 改 动 针 对 的 是 不 同 分 支 : 你 可 以 在 不 同 分 支 间 不 断 地 来 回 切 换 和 工 作, 并 在 时 机 成 熟 时 将 它 们 合 并 起 来 而 所 有 这 些 工 作, 你 需 要 的 命 令 只 有 branch checkout 和 commit

62 Figure 17. 项 目 分 叉 历 史 你 可 以 简 单 地 使 用 git log 命 令 查 看 分 叉 历 史 运 行 git log --oneline --decorate --graph --all, 它 会 输 出 你 的 提 交 历 史 各 个 分 支 的 指 向 以 及 项 目 的 分 支 分 叉 情 况 $ git log --oneline --decorate --graph --all * c2b9e (HEAD, master) made other changes * 87ab2 (testing) made a change / * f30ab add feature #32 - ability to add new formats to the * 34ac2 fixed bug # stack overflow under certain conditions * 98ca9 initial commit of my project 由 于 Git 的 分 支 实 质 上 仅 是 包 含 所 指 对 象 校 验 和 ( 长 度 为 40 的 SHA-1 值 字 符 串 ) 的 文 件, 所 以 它 的 创 建 和 销 毁 都 异 常 高 效 创 建 一 个 新 分 支 就 像 是 往 一 个 文 件 中 写 入 41 个 字 节 (40 个 字 符 和 1 个 换 行 符 ), 如 此 的 简 单 能 不 快 吗? 这 与 过 去 大 多 数 版 本 控 制 系 统 形 成 了 鲜 明 的 对 比, 它 们 在 创 建 分 支 时, 将 所 有 的 项 目 文 件 都 复 制 一 遍, 并 保 存 到 一 个 特 定 的 目 录 完 成 这 样 繁 琐 的 过 程 通 常 需 要 好 几 秒 钟, 有 时 甚 至 需 要 好 几 分 钟 所 需 时 间 的 长 短, 完 全 取 决 于 项 目 的 规 模 而 在 Git 中, 任 何 规 模 的 项 目 都 能 在 瞬 间 创 建 新 分 支 同 时, 由 于 每 次 提 交 都 会 记 录 父 对 象, 所 以 寻 找 恰 当 的 合 并 基 础 ( 译 注 : 即 共 同 祖 先 ) 也 是 同 样 的 简 单 和 高 效 这 些 高 效 的 特 性 使 得 Git 鼓 励 开 发 人 员 频 繁 地 创 建 和 使 用 分 支

63 接 下 来, 让 我 们 看 看 为 什 么 你 应 该 这 么 做? 分 支 的 新 建 与 合 并 让 我 们 来 看 一 个 简 单 的 分 支 新 建 与 分 支 合 并 的 例 子, 实 际 工 作 中 你 可 能 会 用 到 类 似 的 工 作 流 你 将 经 历 如 下 步 骤 : 1. 开 发 某 个 网 站 2. 为 实 现 某 个 新 的 需 求, 创 建 一 个 分 支 3. 在 这 个 分 支 上 开 展 工 作 正 在 此 时, 你 突 然 接 到 一 个 电 话 说 有 个 很 严 重 的 问 题 需 要 紧 急 修 补 你 将 按 照 如 下 方 式 来 处 理 : 1. 切 换 到 你 的 线 上 分 支 (production branch) 2. 为 这 个 紧 急 任 务 新 建 一 个 分 支, 并 在 其 中 修 复 它 3. 在 测 试 通 过 之 后, 切 换 回 线 上 分 支, 然 后 合 并 这 个 修 补 分 支, 最 后 将 改 动 推 送 到 线 上 分 支 4. 切 换 回 你 最 初 工 作 的 分 支 上, 继 续 工 作 新 建 分 支 首 先, 我 们 假 设 你 正 在 你 的 项 目 上 工 作, 并 且 已 经 有 一 些 提 交 Figure 18. 一 个 简 单 提 交 历 史 现 在, 你 已 经 决 定 要 解 决 你 的 公 司 使 用 的 问 题 追 踪 系 统 中 的 #53 问 题 想 要 新 建 一 个 分 支 并 同 时 切 换 到 那 个 分 支 上, 你 可 以 运 行 一 个 带 有 -b 参 数 的 git checkout 命 令 : $ git checkout -b iss53 Switched to a new branch "iss53" 它 是 下 面 两 条 命 令 的 简 写 :

64 $ git branch iss53 $ git checkout iss53 Figure 19. 创 建 一 个 新 分 支 指 针 你 继 续 在 #53 问 题 上 工 作, 并 且 做 了 一 些 提 交 在 此 过 程 中,iss53 分 支 在 不 断 的 向 前 推 进, 因 为 你 已 经 检 出 到 该 分 支 ( 也 就 是 说, 你 的 HEAD 指 针 指 向 了 iss53 分 支 ) $ vim index.html $ git commit -a -m 'added a new footer [issue 53]' Figure 20. iss53 分 支 随 着 工 作 的 进 展 向 前 推 进 现 在 你 接 到 那 个 电 话, 有 个 紧 急 问 题 等 待 你 来 解 决 有 了 Git 的 帮 助, 你 不 必 把 这 个 紧 急 问 题 和 iss53 的 修 改

65 混 在 一 起, 你 也 不 需 要 花 大 力 气 来 还 原 关 于 53# 问 题 的 修 改, 然 后 再 添 加 关 于 这 个 紧 急 问 题 的 修 改, 最 后 将 这 个 修 改 提 交 到 线 上 分 支 你 所 要 做 的 仅 仅 是 切 换 回 master 分 支 但 是, 在 你 这 么 做 之 前, 要 留 意 你 的 工 作 目 录 和 暂 存 区 里 那 些 还 没 有 被 提 交 的 修 改, 它 可 能 会 和 你 即 将 检 出 的 分 支 产 生 冲 突 从 而 阻 止 Git 切 换 到 该 分 支 最 好 的 方 法 是, 在 你 切 换 分 支 之 前, 保 持 好 一 个 干 净 的 状 态 有 一 些 方 法 可 以 绕 过 这 个 问 题 ( 即, 保 存 进 度 (stashing) 和 修 补 提 交 (commit amending)), 我 们 会 在 储 藏 与 清 理 中 看 到 关 于 这 两 个 命 令 的 介 绍 现 在, 我 们 假 设 你 已 经 把 你 的 修 改 全 部 提 交 了, 这 时 你 可 以 切 换 回 master 分 支 了 : $ git checkout master Switched to branch 'master' 这 个 时 候, 你 的 工 作 目 录 和 你 在 开 始 #53 问 题 之 前 一 模 一 样, 现 在 你 可 以 专 心 修 复 紧 急 问 题 了 请 牢 记 : 当 你 切 换 分 支 的 时 候,Git 会 重 置 你 的 工 作 目 录, 使 其 看 起 来 像 回 到 了 你 在 那 个 分 支 上 最 后 一 次 提 交 的 样 子 Git 会 自 动 添 加 删 除 修 改 文 件 以 确 保 此 时 你 的 工 作 目 录 和 这 个 分 支 最 后 一 次 提 交 时 的 样 子 一 模 一 样 接 下 来, 你 要 修 复 这 个 紧 急 问 题 让 我 们 建 立 一 个 针 对 该 紧 急 问 题 的 分 支 (hotfix branch), 在 该 分 支 上 工 作 直 到 问 题 解 决 : $ git checkout -b hotfix Switched to a new branch 'hotfix' $ vim index.html $ git commit -a -m 'fixed the broken address' [hotfix 1fb7853] fixed the broken address 1 file changed, 2 insertions(+)

66 Figure 21. 基 于 master 分 支 的 紧 急 问 题 分 支 hotfix branch 你 可 以 运 行 你 的 测 试, 确 保 你 的 修 改 是 正 确 的, 然 后 将 其 合 并 回 你 的 master 分 支 来 部 署 到 线 上 你 可 以 使 用 git merge 命 令 来 达 到 上 述 目 的 : $ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward index.html file changed, 2 insertions(+) 在 合 并 的 时 候, 你 应 该 注 意 到 了 " 快 进 (fast-forward)" 这 个 词 由 于 当 前 master 分 支 所 指 向 的 提 交 是 你 当 前 提 交 ( 有 关 hotfix 的 提 交 ) 的 直 接 上 游, 所 以 Git 只 是 简 单 的 将 指 针 向 前 移 动 换 句 话 说, 当 你 试 图 合 并 两 个 分 支 时, 如 果 顺 着 一 个 分 支 走 下 去 能 够 到 达 另 一 个 分 支, 那 么 Git 在 合 并 两 者 的 时 候, 只 会 简 单 的 将 指 针 向 前 推 进 ( 指 针 右 移 ), 因 为 这 种 情 况 下 的 合 并 操 作 没 有 需 要 解 决 的 分 歧 这 就 叫 做 快 进 (fast-forward) 现 在, 最 新 的 修 改 已 经 在 master 分 支 所 指 向 的 提 交 快 照 中, 你 可 以 着 手 发 布 该 修 复 了

67 Figure 22. master 被 快 进 到 hotfix 关 于 这 个 紧 急 问 题 的 解 决 方 案 发 布 之 后, 你 准 备 回 到 被 打 断 之 前 时 的 工 作 中 然 而, 你 应 该 先 删 除 hotfix 分 支, 因 为 你 已 经 不 再 需 要 它 了 master 分 支 已 经 指 向 了 同 一 个 位 置 你 可 以 使 用 带 -d 选 项 的 git branch 命 令 来 删 除 分 支 : $ git branch -d hotfix Deleted branch hotfix (3a0874c). 现 在 你 可 以 切 换 回 你 正 在 工 作 的 分 支 继 续 你 的 工 作, 也 就 是 针 对 #53 问 题 的 那 个 分 支 (iss53 分 支 ) $ git checkout iss53 Switched to branch "iss53" $ vim index.html $ git commit -a -m 'finished the new footer [issue 53]' [iss53 ad82d7a] finished the new footer [issue 53] 1 file changed, 1 insertion(+)

68 Figure 23. 继 续 在 iss53 分 支 上 的 工 作 你 在 hotfix 分 支 上 所 做 的 工 作 并 没 有 包 含 到 iss53 分 支 中 如 果 你 需 要 拉 取 hotfix 所 做 的 修 改, 你 可 以 使 用 git merge master 命 令 将 master 分 支 合 并 入 iss53 分 支, 或 者 你 也 可 以 等 到 iss53 分 支 完 成 其 使 命, 再 将 其 合 并 回 master 分 支 分 支 的 合 并 假 设 你 已 经 修 正 了 #53 问 题, 并 且 打 算 将 你 的 工 作 合 并 入 master 分 支 为 此, 你 需 要 合 并 iss53 分 支 到 master 分 支, 这 和 之 前 你 合 并 hotfix 分 支 所 做 的 工 作 差 不 多 你 只 需 要 检 出 到 你 想 合 并 入 的 分 支, 然 后 运 行 git merge 命 令 : $ git checkout master Switched to branch 'master' $ git merge iss53 Merge made by the 'recursive' strategy. index.html file changed, 1 insertion(+) 这 和 你 之 前 合 并 hotfix 分 支 的 时 候 看 起 来 有 一 点 不 一 样 在 这 种 情 况 下, 你 的 开 发 历 史 从 一 个 更 早 的 地 方 开 始 分 叉 开 来 (diverged) 因 为,master 分 支 所 在 提 交 并 不 是 iss53 分 支 所 在 提 交 的 直 接 祖 先,Git 不 得 不 做 一 些 额 外 的 工 作 出 现 这 种 情 况 的 时 候,Git 会 使 用 两 个 分 支 的 末 端 所 指 的 快 照 (C4 和 C5) 以 及 这 两 个 分 支 的 工 作 祖 先 (C2), 做 一 个 简 单 的 三 方 合 并

69 Figure 24. 一 次 典 型 合 并 中 所 用 到 的 三 个 快 照 和 之 间 将 分 支 指 针 向 前 推 进 所 不 同 的 是,Git 将 此 次 三 方 合 并 的 结 果 做 了 一 个 新 的 快 照 并 且 自 动 创 建 一 个 新 的 提 交 指 向 它 这 个 被 称 作 一 次 合 并 提 交, 它 的 特 别 之 处 在 于 他 有 不 止 一 个 父 提 交 Figure 25. 一 个 合 并 提 交 需 要 指 出 的 是,Git 会 自 行 决 定 选 取 哪 一 个 提 交 作 为 最 优 的 共 同 祖 先, 并 以 此 作 为 合 并 的 基 础 ; 这 和 更 加 古 老 的 CVS 系 统 或 者 Subversion (1.5 版 本 之 前 ) 不 同, 在 这 些 古 老 的 版 本 管 理 系 统 中, 用 户 需 要 自 己 选 择 最 佳 的 合 并 基 础 Git 的 这 个 优 势 使 其 在 合 并 操 作 上 比 其 他 系 统 要 简 单 很 多 既 然 你 的 修 改 已 经 合 并 进 来 了, 你 已 经 不 再 需 要 iss53 分 支 了 现 在 你 可 以 在 任 务 追 踪 系 统 中 关 闭 此 项 任 务, 并 删 除 这 个 分 支 $ git branch -d iss53

70 遇 到 冲 突 时 的 分 支 合 并 有 时 候 合 并 操 作 不 会 如 此 顺 利 如 果 你 在 两 个 不 同 的 分 支 中, 对 同 一 个 文 件 的 同 一 个 部 分 进 行 了 不 同 的 修 改,Git 就 没 法 干 净 的 合 并 它 们 如 果 你 对 #53 问 题 的 修 改 和 有 关 hotfix 的 修 改 都 涉 及 到 同 一 个 文 件 的 同 一 处, 在 合 并 它 们 的 时 候 就 会 产 生 合 并 冲 突 : $ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result. 此 时 Git 做 了 合 并, 但 是 没 有 自 动 地 创 建 一 个 新 的 合 并 提 交 Git 会 暂 停 下 来, 等 待 你 去 解 决 合 并 产 生 的 冲 突 你 可 以 在 合 并 冲 突 后 的 任 意 时 刻 使 用 git status 命 令 来 查 看 那 些 因 包 含 合 并 冲 突 而 处 于 未 合 并 (unmerged) 状 态 的 文 件 : $ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a") 任 何 因 包 含 合 并 冲 突 而 有 待 解 决 的 文 件, 都 会 以 未 合 并 状 态 标 识 出 来 Git 会 在 有 冲 突 的 文 件 中 加 入 标 准 的 冲 突 解 决 标 记, 这 样 你 可 以 打 开 这 些 包 含 冲 突 的 文 件 然 后 手 动 解 决 冲 突 出 现 冲 突 的 文 件 会 包 含 一 些 特 殊 区 段, 看 起 来 像 下 面 这 个 样 子 : <<<<<<< HEAD:index.html <div id="footer">contact : .support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53:index.html 这 表 示 HEAD 所 指 示 的 版 本 ( 也 就 是 你 的 master 分 支 所 在 的 位 置, 因 为 你 在 运 行 merge 命 令 的 时 候 已 经 检 出 到 了 这 个 分 支 ) 在 这 个 区 段 的 上 半 部 分 (======= 的 上 半 部 分 ), 而 iss53 分 支 所 指 示 的 版 本 在 ======= 的 下 半 部 分 为 了 解 决 冲 突, 你 必 须 选 择 使 用 由 ======= 分 割 的 两 部 分 中 的 一 个, 或 者 你 也 可 以 自 行 合 并 这 些 内 容 例 如, 你 可 以 通 过 把 这 段 内 容 换 成 下 面 的 样 子 来 解 决 冲 突 :

71 <div id="footer"> please contact us at </div> 上 述 的 冲 突 解 决 方 案 仅 保 留 了 其 中 一 个 分 支 的 修 改, 并 且 <<<<<<<, =======, 和 >>>>>>> 这 些 行 被 完 全 删 除 了 在 你 解 决 了 所 有 文 件 里 的 冲 突 之 后, 对 每 个 文 件 使 用 git add 命 令 来 将 其 标 记 为 冲 突 已 解 决 一 旦 暂 存 这 些 原 本 有 冲 突 的 文 件,Git 就 会 将 它 们 标 记 为 冲 突 已 解 决 如 果 你 想 使 用 图 形 化 工 具 来 解 决 冲 突, 你 可 以 运 行 git mergetool, 该 命 令 会 为 你 启 动 一 个 合 适 的 可 视 化 合 并 工 具, 并 带 领 你 一 步 一 步 解 决 这 些 冲 突 : $ git mergetool This message is displayed because 'merge.tool' is not configured. See 'git mergetool --tool-help' or 'git help config' for more details. 'git mergetool' will now attempt to use one of the following tools: opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge Merging: index.html Normal merge conflict for 'index.html': {local}: modified file {remote}: modified file Hit return to start merge resolution tool (opendiff): 如 果 你 想 使 用 除 默 认 工 具 ( 在 这 里 Git 使 用 opendiff 做 为 默 认 的 合 并 工 具, 因 为 作 者 在 Mac 上 运 行 该 程 序 ) 外 的 其 他 合 并 工 具, 你 可 以 在 下 列 工 具 中 (one of the following tools) 这 句 后 面 看 到 所 有 支 持 的 合 并 工 具 然 后 输 入 你 喜 欢 的 工 具 名 字 就 可 以 了 NOTE 如 果 你 需 要 更 加 高 级 的 工 具 来 解 决 复 杂 的 合 并 冲 突, 我 们 会 在 高 级 合 并 介 绍 更 多 关 于 分 支 合 并 的 内 容 等 你 退 出 合 并 工 具 之 后,Git 会 询 问 刚 才 的 合 并 是 否 成 功 如 果 你 回 答 是,Git 会 暂 存 那 些 文 件 以 表 明 冲 突 已 解 决 : 你 可 以 再 次 运 行 git status 来 确 认 所 有 的 合 并 冲 突 都 已 被 解 决 :

72 $ git status On branch master All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: index.html 如 果 你 对 结 果 感 到 满 意, 并 且 确 定 之 前 有 冲 突 的 的 文 件 都 已 经 暂 存 了, 这 时 你 可 以 输 入 git commit 来 完 成 合 并 提 交 默 认 情 况 下 提 交 信 息 看 起 来 像 下 面 这 个 样 子 : Merge branch 'iss53' Conflicts: index.html # # It looks like you may be committing a merge. # If this is not correct, please remove the file #.git/merge_head # and try again. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # All conflicts fixed but you are still merging. # # Changes to be committed: # modified: index.html # 如 果 你 觉 得 上 述 的 信 息 不 够 充 分, 不 能 完 全 体 现 分 支 合 并 的 过 程, 你 可 以 修 改 上 述 信 息, 添 加 一 些 细 节 给 未 来 检 视 这 个 合 并 的 读 者 一 些 帮 助, 告 诉 他 们 你 是 如 何 解 决 合 并 冲 突 的, 以 及 理 由 是 什 么 分 支 管 理 现 在 已 经 创 建 合 并 删 除 了 一 些 分 支, 让 我 们 看 看 一 些 常 用 的 分 支 管 理 工 具 git branch 命 令 不 只 是 可 以 创 建 与 删 除 分 支 如 果 不 加 任 何 参 数 运 行 它, 会 得 到 当 前 所 有 分 支 的 一 个 列 表 :

73 $ git branch iss53 * master testing 注 意 master 分 支 前 的 * 字 符 : 它 代 表 现 在 检 出 的 那 一 个 分 支 ( 也 就 是 说, 当 前 HEAD 指 针 所 指 向 的 分 支 ) 这 意 味 着 如 果 在 这 时 候 提 交,master 分 支 将 会 随 着 新 的 工 作 向 前 移 动 如 果 需 要 查 看 每 一 个 分 支 的 最 后 一 次 提 交, 可 以 运 行 git branch -v 命 令 : $ git branch -v iss53 93b412c fix javascript issue * master 7a98805 Merge branch 'iss53' testing 782fd34 add scott to the author list in the readmes --merged 与 --no-merged 这 两 个 有 用 的 选 项 可 以 过 滤 这 个 列 表 中 已 经 合 并 或 尚 未 合 并 到 当 前 分 支 的 分 支 如 果 要 查 看 哪 些 分 支 已 经 合 并 到 当 前 分 支, 可 以 运 行 git branch --merged: $ git branch --merged iss53 * master 因 为 之 前 已 经 合 并 了 iss53 分 支, 所 以 现 在 看 到 它 在 列 表 中 在 这 个 列 表 中 分 支 名 字 前 没 有 * 号 的 分 支 通 常 可 以 使 用 git branch -d 删 除 掉 ; 你 已 经 将 它 们 的 工 作 整 合 到 了 另 一 个 分 支, 所 以 并 不 会 失 去 任 何 东 西 查 看 所 有 包 含 未 合 并 工 作 的 分 支, 可 以 运 行 git branch --no-merged: $ git branch --no-merged testing 这 里 显 示 了 其 他 分 支 因 为 它 包 含 了 还 未 合 并 的 工 作, 尝 试 使 用 git branch -d 命 令 删 除 它 时 会 失 败 : $ git branch -d testing error: The branch 'testing' is not fully merged. If you are sure you want to delete it, run 'git branch -D testing'. 如 果 真 的 想 要 删 除 分 支 并 丢 掉 那 些 工 作, 如 同 帮 助 信 息 里 所 指 出 的, 可 以 使 用 -D 选 项 强 制 删 除 它

74 分 支 开 发 工 作 流 现 在 你 已 经 学 会 新 建 和 合 并 分 支, 那 么 你 可 以 或 者 应 该 用 它 来 做 些 什 么 呢? 在 本 节, 我 们 会 介 绍 一 些 常 见 的 利 用 分 支 进 行 开 发 的 工 作 流 程 而 正 是 由 于 分 支 管 理 的 便 捷, 才 衍 生 出 这 些 典 型 的 工 作 模 式, 你 可 以 根 据 项 目 实 际 情 况 选 择 一 种 用 用 看 长 期 分 支 因 为 Git 使 用 简 单 的 三 方 合 并, 所 以 就 算 在 一 段 较 长 的 时 间 内, 反 复 把 一 个 分 支 合 并 入 另 一 个 分 支, 也 不 是 什 么 难 事 也 就 是 说, 在 整 个 项 目 开 发 周 期 的 不 同 阶 段, 你 可 以 同 时 拥 有 多 个 开 放 的 分 支 ; 你 可 以 定 期 地 把 某 些 特 性 分 支 合 并 入 其 他 分 支 中 许 多 使 用 Git 的 开 发 者 都 喜 欢 使 用 这 种 方 式 来 工 作, 比 如 只 在 master 分 支 上 保 留 完 全 稳 定 的 代 码 有 可 能 仅 仅 是 已 经 发 布 或 即 将 发 布 的 代 码 他 们 还 有 一 些 名 为 develop 或 者 next 的 平 行 分 支, 被 用 来 做 后 续 开 发 或 者 测 试 稳 定 性 这 些 分 支 不 必 保 持 绝 对 稳 定, 但 是 一 旦 达 到 稳 定 状 态, 它 们 就 可 以 被 合 并 入 master 分 支 了 这 样, 在 确 保 这 些 已 完 成 的 特 性 分 支 ( 短 期 分 支, 比 如 之 前 的 iss53 分 支 ) 能 够 通 过 所 有 测 试, 并 且 不 会 引 入 更 多 bug 之 后, 就 可 以 合 并 入 主 干 分 支 中, 等 待 下 一 次 的 发 布 事 实 上 我 们 刚 才 讨 论 的, 是 随 着 你 的 提 交 而 不 断 右 移 的 指 针 稳 定 分 支 的 指 针 总 是 在 提 交 历 史 中 落 后 一 大 截, 而 前 沿 分 支 的 指 针 往 往 比 较 靠 前 Figure 26. 渐 进 稳 定 分 支 的 线 性 图 通 常 把 他 们 想 象 成 流 水 线 (work silos) 可 能 更 好 理 解 一 点, 那 些 经 过 测 试 考 验 的 提 交 会 被 遴 选 到 更 加 稳 定 的 流 水 线 上 去 Figure 27. 渐 进 稳 定 分 支 的 流 水 线 ( silo ) 视 图 你 可 以 用 这 种 方 法 维 护 不 同 层 次 的 稳 定 性 一 些 大 型 项 目 还 有 一 个 proposed( 建 议 ) 或 pu: proposed

75 updates( 建 议 更 新 ) 分 支, 它 可 能 因 包 含 一 些 不 成 熟 的 内 容 而 不 能 进 入 next 或 者 master 分 支 这 么 做 的 目 的 是 使 你 的 分 支 具 有 不 同 级 别 的 稳 定 性 ; 当 它 们 具 有 一 定 程 度 的 稳 定 性 后, 再 把 它 们 合 并 入 具 有 更 高 级 别 稳 定 性 的 分 支 中 再 次 强 调 一 下, 使 用 多 个 长 期 分 支 的 方 法 并 非 必 要, 但 是 这 么 做 通 常 很 有 帮 助, 尤 其 是 当 你 在 一 个 非 常 庞 大 或 者 复 杂 的 项 目 中 工 作 时 特 性 分 支 特 性 分 支 对 任 何 规 模 的 项 目 都 适 用 特 性 分 支 是 一 种 短 期 分 支, 它 被 用 来 实 现 单 一 特 性 或 其 相 关 工 作 也 许 你 从 来 没 有 在 其 他 的 版 本 控 制 系 统 (VCS) 上 这 么 做 过, 因 为 在 那 些 版 本 控 制 系 统 中 创 建 和 合 并 分 支 通 常 很 费 劲 然 而, 在 Git 中 一 天 之 内 多 次 创 建 使 用 合 并 删 除 分 支 都 很 常 见 你 已 经 在 上 一 节 中 你 创 建 的 iss53 和 hotfix 特 性 分 支 中 看 到 过 这 种 用 法 你 在 上 一 节 用 到 的 特 性 分 支 (iss53 和 hotfix 分 支 ) 中 提 交 了 一 些 更 新, 并 且 在 它 们 合 并 入 主 干 分 支 之 后, 你 又 删 除 了 它 们 这 项 技 术 能 使 你 快 速 并 且 完 整 地 进 行 上 下 文 切 换 (context-switch) 因 为 你 的 工 作 被 分 散 到 不 同 的 流 水 线 中, 在 不 同 的 流 水 线 中 每 个 分 支 都 仅 与 其 目 标 特 性 相 关, 因 此, 在 做 代 码 审 查 之 类 的 工 作 的 时 候 就 能 更 加 容 易 地 看 出 你 做 了 哪 些 改 动 你 可 以 把 做 出 的 改 动 在 特 性 分 支 中 保 留 几 分 钟 几 天 甚 至 几 个 月, 等 它 们 成 熟 之 后 再 合 并, 而 不 用 在 乎 它 们 建 立 的 顺 序 或 工 作 进 度 考 虑 这 样 一 个 例 子, 你 在 master 分 支 上 工 作 到 C1, 这 时 为 了 解 决 一 个 问 题 而 新 建 iss91 分 支, 在 iss91 分 支 上 工 作 到 C4, 然 而 对 于 那 个 问 题 你 又 有 了 新 的 想 法, 于 是 你 再 新 建 一 个 iss91v2 分 支 试 图 用 另 一 种 方 法 解 决 那 个 问 题, 接 着 你 回 到 master 分 支 工 作 了 一 会 儿, 你 又 冒 出 了 一 个 不 太 确 定 的 想 法, 你 便 在 C10 的 时 候 新 建 一 个 dumbidea 分 支, 并 在 上 面 做 些 实 验 你 的 提 交 历 史 看 起 来 像 下 面 这 个 样 子 :

76 Figure 28. 拥 有 多 个 特 性 分 支 的 提 交 历 史 现 在, 我 们 假 设 两 件 事 情 : 你 决 定 使 用 第 二 个 方 案 来 解 决 那 个 问 题, 即 使 用 在 iss91v2 分 支 中 方 案 ; 另 外, 你 将 dumbidea 分 支 拿 给 你 的 同 事 看 过 之 后, 结 果 发 现 这 是 个 惊 人 之 举 这 时 你 可 以 抛 弃 iss91 分 支 ( 即 丢 弃 C5 和 C6 提 交 ), 然 后 把 另 外 两 个 分 支 合 并 入 主 干 分 支 最 终 你 的 提 交 历 史 看 起 来 像 下 面 这 个 样 子 :

77 Figure 29. 合 并 了 dumbidea 和 iss91v2 分 支 之 后 的 提 交 历 史 我 们 将 会 在 分 布 式 Git 中 向 你 揭 示 更 多 有 关 分 支 工 作 流 的 细 节, 因 此, 请 确 保 你 阅 读 完 那 个 章 节 之 后, 再 来 决 定 你 的 下 个 项 目 要 使 用 什 么 样 的 分 支 策 略 (branching scheme) 请 牢 记, 当 你 做 这 么 多 操 作 的 时 候, 这 些 分 支 全 部 都 存 于 本 地 当 你 新 建 和 合 并 分 支 的 时 候, 所 有 这 一 切 都 只 发 生 在 你 本 地 的 Git 版 本 库 中 没 有 与 服 务 器 发 生 交 互

78 远 程 分 支 远 程 引 用 是 对 远 程 仓 库 的 引 用 ( 指 针 ), 包 括 分 支 标 签 等 等 你 可 以 通 过 git ls-remote (remote) 来 显 式 地 获 得 远 程 引 用 的 完 整 列 表, 或 者 通 过 git remote show (remote) 获 得 远 程 分 支 的 更 多 信 息 然 而, 一 个 更 常 见 的 做 法 是 利 用 远 程 跟 踪 分 支 远 程 跟 踪 分 支 是 远 程 分 支 状 态 的 引 用 它 们 是 你 不 能 移 动 的 本 地 引 用, 当 你 做 任 何 网 络 通 信 操 作 时, 它 们 会 自 动 移 动 远 程 跟 踪 分 支 像 是 你 上 次 连 接 到 远 程 仓 库 时, 那 些 分 支 所 处 状 态 的 书 签 它 们 以 (remote)/(branch) 形 式 命 名 例 如, 如 果 你 想 要 看 你 最 后 一 次 与 远 程 仓 库 origin 通 信 时 master 分 支 的 状 态, 你 可 以 查 看 origin/master 分 支 你 与 同 事 合 作 解 决 一 个 问 题 并 且 他 们 推 送 了 一 个 iss53 分 支, 你 可 能 有 自 己 的 本 地 iss53 分 支 ; 但 是 在 服 务 器 上 的 分 支 会 指 向 origin/iss53 的 提 交 这 可 能 有 一 点 儿 难 以 理 解, 让 我 们 来 看 一 个 例 子 假 设 你 的 网 络 里 有 一 个 在 git.ourcompany.com 的 Git 服 务 器 如 果 你 从 这 里 克 隆,Git 的 clone 命 令 会 为 你 自 动 将 其 命 名 为 origin, 拉 取 它 的 所 有 数 据, 创 建 一 个 指 向 它 的 master 分 支 的 指 针, 并 且 在 本 地 将 其 命 名 为 origin/master Git 也 会 给 你 一 个 与 origin 的 master 分 支 在 指 向 同 一 个 地 方 的 本 地 master 分 支, 这 样 你 就 有 工 作 的 基 础 origin 并 无 特 殊 含 义 NOTE 远 程 仓 库 名 字 origin 与 分 支 名 字 master 一 样, 在 Git 中 并 没 有 任 何 特 别 的 含 义 一 样 同 时 master 是 当 你 运 行 git init 时 默 认 的 起 始 分 支 名 字, 原 因 仅 仅 是 它 的 广 泛 使 用, origin 是 当 你 运 行 git clone 时 默 认 的 远 程 仓 库 名 字 如 果 你 运 行 git clone -o booyah, 那 么 你 默 认 的 远 程 分 支 名 字 将 会 是 booyah/master

79 Figure 30. 克 隆 之 后 的 服 务 器 与 本 地 仓 库 如 果 你 在 本 地 的 master 分 支 做 了 一 些 工 作, 然 而 在 同 一 时 间, 其 他 人 推 送 提 交 到 git.ourcompany.com 并 更 新 了 它 的 master 分 支, 那 么 你 的 提 交 历 史 将 向 不 同 的 方 向 前 进 也 许, 只 要 你 不 与 origin 服 务 器 连 接, 你 的 origin/master 指 针 就 不 会 移 动

80 Figure 31. 本 地 与 远 程 的 工 作 可 以 分 叉 如 果 要 同 步 你 的 工 作, 运 行 git fetch origin 命 令 这 个 命 令 查 找 origin 是 哪 一 个 服 务 器 ( 在 本 例 中, 它 是 git.ourcompany.com), 从 中 抓 取 本 地 没 有 的 数 据, 并 且 更 新 本 地 数 据 库, 移 动 origin/master 指 针 指 向 新 的 更 新 后 的 位 置

81 Figure 32. git fetch 更 新 你 的 远 程 仓 库 引 用 为 了 演 示 有 多 个 远 程 仓 库 与 远 程 分 支 的 情 况, 我 们 假 定 你 有 另 一 个 内 部 Git 服 务 器, 仅 用 于 你 的 sprint 小 组 的 开 发 工 作 这 个 服 务 器 位 于 git.team1.ourcompany.com 你 可 以 运 行 git remote add 命 令 添 加 一 个 新 的 远 程 仓 库 引 用 到 当 前 的 项 目, 这 个 命 令 我 们 会 在 Git 基 础 中 详 细 说 明 将 这 个 远 程 仓 库 命 名 为 teamone, 将 其 作 为 整 个 URL 的 缩 写

82 Figure 33. 添 加 另 一 个 远 程 仓 库 现 在, 可 以 运 行 git fetch teamone 来 抓 取 远 程 仓 库 teamone 有 而 本 地 没 有 的 数 据 因 为 那 台 服 务 器 上 现 有 的 数 据 是 origin 服 务 器 上 的 一 个 子 集, 所 以 Git 并 不 会 抓 取 数 据 而 是 会 设 置 远 程 跟 踪 分 支 teamone/master 指 向 teamone 的 master 分 支

83 Figure 34. 远 程 跟 踪 分 支 teamone/master 推 送 当 你 想 要 公 开 分 享 一 个 分 支 时, 需 要 将 其 推 送 到 有 写 入 权 限 的 远 程 仓 库 上 本 地 的 分 支 并 不 会 自 动 与 远 程 仓 库 同 步 - 你 必 须 显 式 地 推 送 想 要 分 享 的 分 支 这 样, 你 就 可 以 把 不 愿 意 分 享 的 内 容 放 到 私 人 分 支 上, 而 将 需 要 和 别 人 协 作 的 内 容 推 送 到 公 开 分 支 如 果 希 望 和 别 人 一 起 在 名 为 serverfix 的 分 支 上 工 作, 你 可 以 像 推 送 第 一 个 分 支 那 样 推 送 它 运 行 git push (remote) (branch): $ git push origin serverfix Counting objects: 24, done. Delta compression using up to 8 threads. Compressing objects: 100% (15/15), done. Writing objects: 100% (24/24), 1.91 KiB 0 bytes/s, done. Total 24 (delta 2), reused 0 (delta 0) To * [new branch] serverfix -> serverfix

84 这 里 有 些 工 作 被 简 化 了 Git 自 动 将 serverfix 分 支 名 字 展 开 为 refs/heads/serverfix:refs/heads/serverfix, 那 意 味 着, 推 送 本 地 的 serverfix 分 支 来 更 新 远 程 仓 库 上 的 serverfix 分 支 我 们 将 会 详 细 学 习 Git 内 部 原 理 的 refs/heads/ 部 分, 但 是 现 在 可 以 先 把 它 放 在 儿 你 也 可 以 运 行 git push origin serverfix:serverfix, 它 会 做 同 样 的 事 - 相 当 于 它 说, 推 送 本 地 的 serverfix 分 支, 将 其 作 为 远 程 仓 库 的 serverfix 分 支 可 以 通 过 这 种 格 式 来 推 送 本 地 分 支 到 一 个 命 名 不 相 同 的 远 程 分 支 如 果 并 不 想 让 远 程 仓 库 上 的 分 支 叫 做 serverfix, 可 以 运 行 git push origin serverfix:awesomebranch 来 将 本 地 的 serverfix 分 支 推 送 到 远 程 仓 库 上 的 awesomebranch 分 支 如 何 避 免 每 次 输 入 密 码 如 果 你 正 在 使 用 HTTPS URL 来 推 送,Git 服 务 器 会 询 问 用 户 名 与 密 码 默 认 情 况 下 它 会 在 终 端 中 提 示 服 务 器 是 否 允 许 你 进 行 推 送 NOTE 如 果 不 想 在 每 一 次 推 送 时 都 输 入 用 户 名 与 密 码, 你 可 以 设 置 一 个 credential cache 最 简 单 的 方 式 就 是 将 其 保 存 在 内 存 中 几 分 钟, 可 以 简 单 地 运 行 git config --global credential.helper cache 来 设 置 它 想 要 了 解 更 多 关 于 不 同 验 证 缓 存 的 可 用 选 项, 查 看 凭 证 存 储 下 一 次 其 他 协 作 者 从 服 务 器 上 抓 取 数 据 时, 他 们 会 在 本 地 生 成 一 个 远 程 分 支 origin/serverfix, 指 向 服 务 器 的 serverfix 分 支 的 引 用 : $ git fetch origin remote: Counting objects: 7, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 3 (delta 0) Unpacking objects: 100% (3/3), done. From * [new branch] serverfix -> origin/serverfix 要 特 别 注 意 的 一 点 是 当 抓 取 到 新 的 远 程 跟 踪 分 支 时, 本 地 不 会 自 动 生 成 一 份 可 编 辑 的 副 本 ( 拷 贝 ) 换 一 句 话 说, 这 种 情 况 下, 不 会 有 一 个 新 的 serverfix 分 支 - 只 有 一 个 不 可 以 修 改 的 origin/serverfix 指 针 可 以 运 行 git merge origin/serverfix 将 这 些 工 作 合 并 到 当 前 所 在 的 分 支 如 果 想 要 在 自 己 的 serverfix 分 支 上 工 作, 可 以 将 其 建 立 在 远 程 跟 踪 分 支 之 上 : $ git checkout -b serverfix origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix' 这 会 给 你 一 个 用 于 工 作 的 本 地 分 支, 并 且 起 点 位 于 origin/serverfix

85 跟 踪 分 支 从 一 个 远 程 跟 踪 分 支 检 出 一 个 本 地 分 支 会 自 动 创 建 一 个 叫 做 跟 踪 分 支 ( 有 时 候 也 叫 做 上 游 分 支 ) 跟 踪 分 支 是 与 远 程 分 支 有 直 接 关 系 的 本 地 分 支 如 果 在 一 个 跟 踪 分 支 上 输 入 git pull,git 能 自 动 地 识 别 去 哪 个 服 务 器 上 抓 取 合 并 到 哪 个 分 支 当 克 隆 一 个 仓 库 时, 它 通 常 会 自 动 地 创 建 一 个 跟 踪 origin/master 的 master 分 支 然 而, 如 果 你 愿 意 的 话 可 以 设 置 其 他 的 跟 踪 分 支 - 其 他 远 程 仓 库 上 的 跟 踪 分 支, 或 者 不 跟 踪 master 分 支 最 简 单 的 就 是 之 前 看 到 的 例 子, 运 行 git checkout -b [branch] [remotename]/[branch] 这 是 一 个 十 分 常 用 的 操 作 所 以 Git 提 供 了 --track 快 捷 方 式 : $ git checkout --track origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. Switched to a new branch 'serverfix' 如 果 想 要 将 本 地 分 支 与 远 程 分 支 设 置 为 不 同 名 字, 你 可 以 轻 松 地 增 加 一 个 不 同 名 字 的 本 地 分 支 的 上 一 个 命 令 : $ git checkout -b sf origin/serverfix Branch sf set up to track remote branch serverfix from origin. Switched to a new branch 'sf' 现 在, 本 地 分 支 sf 会 自 动 从 origin/serverfix 拉 取 设 置 已 有 的 本 地 分 支 跟 踪 一 个 刚 刚 拉 取 下 来 的 远 程 分 支, 或 者 想 要 修 改 正 在 跟 踪 的 上 游 分 支, 你 可 以 在 任 意 时 间 使 用 -u 或 --set-upstream-to 选 项 运 行 git branch 来 显 式 地 设 置 $ git branch -u origin/serverfix Branch serverfix set up to track remote branch serverfix from origin. 上 游 快 捷 方 式 NOTE 当 设 置 好 跟 踪 分 支 后, 可 以 通 快 捷 方 式 来 引 用 它 所 以 在 master 分 支 时 并 且 它 正 在 跟 踪 origin/master 时, 如 果 愿 意 的 话 可 以 使 用 git 来 取 代 git merge origin/master 如 果 想 要 查 看 设 置 的 所 有 跟 踪 分 支, 可 以 使 用 git branch 的 -vv 选 项 这 会 将 所 有 的 本 地 分 支 列 出 来 并 且 包 含 更 多 的 信 息, 如 每 一 个 分 支 正 在 跟 踪 哪 个 远 程 分 支 与 本 地 分 支 是 否 是 领 先 落 后 或 是 都 有

86 $ git branch -vv iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets master 1ae2a45 [origin/master] deploying index fix * serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it testing 5ea463a trying something new 这 里 可 以 看 到 iss53 分 支 正 在 跟 踪 origin/iss53 并 且 ahead 是 2, 意 味 着 本 地 有 两 个 提 交 还 没 有 推 送 到 服 务 器 上 也 能 看 到 master 分 支 正 在 跟 踪 origin/master 分 支 并 且 是 最 新 的 接 下 来 可 以 看 到 serverfix 分 支 正 在 跟 踪 teamone 服 务 器 上 的 server-fix-good 分 支 并 且 领 先 2 落 后 1, 意 味 着 服 务 器 上 有 一 次 提 交 还 没 有 合 并 入 同 时 本 地 有 三 次 提 交 还 没 有 推 送 最 后 看 到 testing 分 支 并 没 有 跟 踪 任 何 远 程 分 支 需 要 重 点 注 意 的 一 点 是 这 些 数 字 的 值 来 自 于 你 从 每 个 服 务 器 上 最 后 一 次 抓 取 的 数 据 这 个 命 令 并 没 有 连 接 服 务 器, 它 只 会 告 诉 你 关 于 本 地 缓 存 的 服 务 器 数 据 如 果 想 要 统 计 最 新 的 领 先 与 落 后 数 字, 需 要 在 运 行 此 命 令 前 抓 取 所 有 的 远 程 仓 库 可 以 像 这 样 做 :$ git fetch --all; git branch -vv 拉 取 当 git fetch 命 令 从 服 务 器 上 抓 取 本 地 没 有 的 数 据 时, 它 并 不 会 修 改 工 作 目 录 中 的 内 容 它 只 会 获 取 数 据 然 后 让 你 自 己 合 并 然 而, 有 一 个 命 令 叫 作 git pull 在 大 多 数 情 况 下 它 的 含 义 是 一 个 git fetch 紧 接 着 一 个 git merge 命 令 如 果 有 一 个 像 之 前 章 节 中 演 示 的 设 置 好 的 跟 踪 分 支, 不 管 它 是 显 式 地 设 置 还 是 通 过 clone 或 checkout 命 令 为 你 创 建 的,git pull 都 会 查 找 当 前 分 支 所 跟 踪 的 服 务 器 与 分 支, 从 服 务 器 上 抓 取 数 据 然 后 尝 试 合 并 入 那 个 远 程 分 支 由 于 git pull 的 魔 法 经 常 令 人 困 惑 所 以 通 常 单 独 显 式 地 使 用 fetch 与 merge 命 令 会 更 好 一 些 删 除 远 程 分 支 假 设 你 已 经 通 过 远 程 分 支 做 完 所 有 的 工 作 了 - 也 就 是 说 你 和 你 的 协 作 者 已 经 完 成 了 一 个 特 性 并 且 将 其 合 并 到 了 远 程 仓 库 的 master 分 支 ( 或 任 何 其 他 稳 定 代 码 分 支 ) 可 以 运 行 带 有 --delete 选 项 的 git push 命 令 来 删 除 一 个 远 程 分 支 如 果 想 要 从 服 务 器 上 删 除 serverfix 分 支, 运 行 下 面 的 命 令 : $ git push origin --delete serverfix To - [deleted] serverfix 基 本 上 这 个 命 令 做 的 只 是 从 服 务 器 上 移 除 这 个 指 针 Git 服 务 器 通 常 会 保 留 数 据 一 段 时 间 直 到 垃 圾 回 收 运 行, 所 以 如 果 不 小 心 删 除 掉 了, 通 常 是 很 容 易 恢 复 的 变 基 在 Git 中 整 合 来 自 不 同 分 支 的 修 改 主 要 有 两 种 方 法 :merge 以 及 rebase 在 本 节 中 我 们 将 学 习 什 么 是 变 基, 怎 样 使 用 变 基, 并 将 展 示 该 操 作 的 惊 艳 之 处, 以 及 指 出 在 何 种 情 况 下 你 应 避 免 使 用 它

87 变 基 的 基 本 操 作 请 回 顾 之 前 在 分 支 的 合 并 中 的 一 个 例 子, 你 会 看 到 开 发 任 务 分 叉 到 两 个 不 同 分 支, 又 各 自 提 交 了 更 新 Figure 35. 分 叉 的 提 交 历 史 之 前 介 绍 过, 整 合 分 支 最 容 易 的 方 法 是 merge 命 令 它 会 把 两 个 分 支 的 最 新 快 照 (C3 和 C4) 以 及 二 者 最 近 的 共 同 祖 先 (C2) 进 行 三 方 合 并, 合 并 的 结 果 是 生 成 一 个 新 的 快 照 ( 并 提 交 ) Figure 36. 通 过 合 并 操 作 来 整 合 分 叉 了 的 历 史 其 实, 还 有 一 种 方 法 : 你 可 以 提 取 在 C4 中 引 入 的 补 丁 和 修 改, 然 后 在 C3 的 基 础 上 再 应 用 一 次 在 Git 中, 这 种 操 作 就 叫 做 变 基 你 可 以 使 用 rebase 命 令 将 提 交 到 某 一 分 支 上 的 所 有 修 改 都 移 至 另 一 分 支 上, 就 好 像 重 新 播 放 一 样 在 上 面 这 个 例 子 中, 运 行 :

88 $ git checkout experiment $ git rebase master First, rewinding head to replay your work on top of it... Applying: added staged command 它 的 原 理 是 首 先 找 到 这 两 个 分 支 ( 即 当 前 分 支 experiment 变 基 操 作 的 目 标 基 底 分 支 master) 的 最 近 共 同 祖 先 C2, 然 后 对 比 当 前 分 支 相 对 于 该 祖 先 的 历 次 提 交, 提 取 相 应 的 修 改 并 存 为 临 时 文 件, 然 后 将 当 前 分 支 指 向 目 标 基 底 C3, 最 后 以 此 将 之 前 另 存 为 临 时 文 件 的 修 改 依 序 应 用 ( 译 注 : 写 明 了 commit id, 以 便 理 解, 下 同 ) Figure 37. 将 C4 中 的 修 改 变 基 到 C3 上 现 在 回 到 master 分 支, 进 行 一 次 快 进 合 并 $ git checkout master $ git merge experiment Figure 38. master 分 支 的 快 进 合 并 此 时,C4' 指 向 的 快 照 就 和 上 面 使 用 merge 命 令 的 例 子 中 C5 指 向 的 快 照 一 模 一 样 了 这 两 种 整 合 方 法 的 最 终 结 果 没 有 任 何 区 别, 但 是 变 基 使 得 提 交 历 史 更 加 整 洁 你 在 查 看 一 个 经 过 变 基 的 分 支 的 历 史 记 录 时 会 发 现, 尽 管 实 际 的 开 发 工 作 是 并 行 的, 但 它 们 看 上 去 就 像 是 先 后 串 行 的 一 样, 提 交 历 史 是 一 条 直 线 没 有 分 叉 一 般 我 们 这 样 做 的 目 的 是 为 了 确 保 在 向 远 程 分 支 推 送 时 能 保 持 提 交 历 史 的 整 洁 例 如 向 某 个 别 人 维 护 的 项 目 贡

89 献 代 码 时 在 这 种 情 况 下, 你 首 先 在 自 己 的 分 支 里 进 行 开 发, 当 开 发 完 成 时 你 需 要 先 将 你 的 代 码 变 基 到 origin/master 上, 然 后 再 向 主 项 目 提 交 修 改 这 样 的 话, 该 项 目 的 维 护 者 就 不 再 需 要 进 行 整 合 工 作, 只 需 要 快 进 合 并 便 可 请 注 意, 无 论 是 通 过 变 基, 还 是 通 过 三 方 合 并, 整 合 的 最 终 结 果 所 指 向 的 快 照 始 终 是 一 样 的, 只 不 过 提 交 历 史 不 同 罢 了 变 基 是 将 一 系 列 提 交 按 照 原 有 次 序 依 次 应 用 到 另 一 分 支 上, 而 合 并 是 把 最 终 结 果 合 在 一 起 更 有 趣 的 变 基 例 子 在 对 两 个 分 支 进 行 变 基 时, 所 生 成 的 重 演 并 不 一 定 要 在 目 标 分 支 上 应 用, 你 也 可 以 指 定 另 外 的 一 个 分 支 进 行 应 用 就 像 从 一 个 特 性 分 支 里 再 分 出 一 个 特 性 分 支 的 提 交 历 史 中 的 例 子 这 样 你 创 建 了 一 个 特 性 分 支 server, 为 服 务 端 添 加 了 一 些 功 能, 提 交 了 C3 和 C4 然 后 从 C3 上 创 建 了 特 性 分 支 client, 为 客 户 端 添 加 了 一 些 功 能, 提 交 了 C8 和 C9 最 后, 你 回 到 server 分 支, 又 提 交 了 C10 Figure 39. 从 一 个 特 性 分 支 里 再 分 出 一 个 特 性 分 支 的 提 交 历 史 假 设 你 希 望 将 client 中 的 修 改 合 并 到 主 分 支 并 发 布, 但 暂 时 并 不 想 合 并 server 中 的 修 改, 因 为 它 们 还 需 要 经 过 更 全 面 的 测 试 这 时, 你 就 可 以 使 用 git rebase 命 令 的 --onto 选 项, 选 中 在 client 分 支 里 但 不 在 server 分 支 里 的 修 改 ( 即 C8 和 C9), 将 它 们 在 master 分 支 上 重 演 : $ git rebase --onto master server client 以 上 命 令 的 意 思 是 : 取 出 client 分 支, 找 出 处 于 client 分 支 和 server 分 支 的 共 同 祖 先 之 后 的 修 改, 然 后 把 它 们 在 master 分 支 上 重 演 一 遍 这 理 解 起 来 有 一 点 复 杂, 不 过 效 果 非 常 酷

90 Figure 40. 截 取 特 性 分 支 上 的 另 一 个 特 性 分 支, 然 后 变 基 到 其 他 分 支 现 在 可 以 快 进 合 并 master 分 支 了 ( 如 图 快 进 合 并 master 分 支, 使 之 包 含 来 自 client 分 支 的 修 改 ): $ git checkout master $ git merge client Figure 41. 快 进 合 并 master 分 支, 使 之 包 含 来 自 client 分 支 的 修 改 接 下 来 你 决 定 将 server 分 支 中 的 修 改 也 整 合 进 来 使 用 git rebase [basebranch] [topicbranch] 命 令 可 以 直 接 将 特 性 分 支 ( 即 本 例 中 的 server) 变 基 到 目 标 分 支 ( 即 master) 上 这 样 做 能 省 去 你 先 切 换 到 server 分 支, 再 对 其 执 行 变 基 命 令 的 多 个 步 骤 $ git rebase master server 如 图 将 server 中 的 修 改 变 基 到 master 上 所 示,server 中 的 代 码 被 续 到 了 master 后 面

91 Figure 42. 将 server 中 的 修 改 变 基 到 master 上 然 后 就 可 以 快 进 合 并 主 分 支 master 了 : $ git checkout master $ git merge server 至 此,client 和 server 分 支 中 的 修 改 都 已 经 整 合 到 主 分 支 里 去 了, 你 可 以 删 除 这 两 个 分 支, 最 终 提 交 历 史 会 变 成 图 最 终 的 提 交 历 史 中 的 样 子 : $ git branch -d client $ git branch -d server Figure 43. 最 终 的 提 交 历 史 变 基 的 风 险 呃, 奇 妙 的 变 基 也 并 非 完 美 无 缺, 要 用 它 得 遵 守 一 条 准 则 : 不 要 对 在 你 的 仓 库 外 有 副 本 的 分 支 执 行 变 基 如 果 你 遵 循 这 条 金 科 玉 律, 就 不 会 出 差 错 否 则, 人 民 群 众 会 仇 恨 你, 你 的 朋 友 和 家 人 也 会 嘲 笑 你, 唾 弃 你 变 基 操 作 的 实 质 是 丢 弃 一 些 现 有 的 提 交, 然 后 相 应 地 新 建 一 些 内 容 一 样 但 实 际 上 不 同 的 提 交 如 果 你 已 经 将 提 交 推 送 至 某 个 仓 库, 而 其 他 人 也 已 经 从 该 仓 库 拉 取 提 交 并 进 行 了 后 续 工 作, 此 时, 如 果 你 用 git rebase 命 令 重 新 整 理 了 提 交 并 再 次 推 送, 你 的 同 伴 因 此 将 不 得 不 再 次 将 他 们 手 头 的 工 作 与 你 的 提 交 进 行 整 合, 如 果 接 下 来 你 还 要 拉 取 并 整 合 他 们 修 改 过 的 提 交, 事 情 就 会 变 得 一 团 糟 让 我 们 来 看 一 个 在 公 开 的 仓 库 上 执 行 变 基 操 作 所 带 来 的 问 题 假 设 你 从 一 个 中 央 服 务 器 克 隆 然 后 在 它 的 基 础 上 进 行 了 一 些 开 发 你 的 提 交 历 史 如 图 所 示 :

92 Figure 44. 克 隆 一 个 仓 库, 然 后 在 它 的 基 础 上 进 行 了 一 些 开 发 然 后, 某 人 又 向 中 央 服 务 器 提 交 了 一 些 修 改, 其 中 还 包 括 一 次 合 并 你 抓 取 了 这 些 在 远 程 分 支 上 的 修 改, 并 将 其 合 并 到 你 本 地 的 开 发 分 支, 然 后 你 的 提 交 历 史 就 会 变 成 这 样 :

93 Figure 45. 抓 取 别 人 的 提 交, 合 并 到 自 己 的 开 发 分 支 接 下 来, 这 个 人 又 决 定 把 合 并 操 作 回 滚, 改 用 变 基 ; 继 而 又 用 git push --force 命 令 覆 盖 了 服 务 器 上 的 提 交 历 史 之 后 你 从 服 务 器 抓 取 更 新, 会 发 现 多 出 来 一 些 新 的 提 交

94 Figure 46. 有 人 推 送 了 经 过 变 基 的 提 交, 并 丢 弃 了 你 的 本 地 开 发 所 基 于 的 一 些 提 交 结 果 就 是 你 们 两 人 的 处 境 都 十 分 尴 尬 如 果 你 执 行 git pull 命 令, 你 将 合 并 来 自 两 条 提 交 历 史 的 内 容, 生 成 一 个 新 的 合 并 提 交, 最 终 仓 库 会 如 图 所 示 : Figure 47. 你 将 相 同 的 内 容 又 合 并 了 一 次, 生 成 了 一 个 新 的 提 交

95 此 时 如 果 你 执 行 git log 命 令, 你 会 发 现 有 两 个 提 交 的 作 者 日 期 日 志 居 然 是 一 样 的, 这 会 令 人 感 到 混 乱 此 外, 如 果 你 将 这 一 堆 又 推 送 到 服 务 器 上, 你 实 际 上 是 将 那 些 已 经 被 变 基 抛 弃 的 提 交 又 找 了 回 来, 这 会 令 人 感 到 更 加 混 乱 很 明 显 对 方 并 不 想 在 提 交 历 史 中 看 到 C4 和 C6, 因 为 之 前 就 是 他 们 把 这 两 个 提 交 通 过 变 基 丢 弃 的 用 变 基 解 决 变 基 如 果 你 真 的 遭 遇 了 类 似 的 处 境,Git 还 有 一 些 高 级 魔 法 可 以 帮 到 你 如 果 团 队 中 的 某 人 强 制 推 送 并 覆 盖 了 一 些 你 所 基 于 的 提 交, 你 需 要 做 的 就 是 检 查 你 做 了 哪 些 修 改, 以 及 他 们 覆 盖 了 哪 些 修 改 实 际 上,Git 除 了 对 整 个 提 交 计 算 SHA-1 校 验 和 以 外, 也 对 本 次 提 交 所 引 入 的 修 改 计 算 了 校 验 和 即 patchid 如 果 你 拉 取 被 覆 盖 过 的 更 新 并 将 你 手 头 的 工 作 基 于 此 进 行 变 基 的 话, 一 般 情 况 下 Git 都 能 成 功 分 辨 出 哪 些 是 你 的 修 改, 并 把 它 们 应 用 到 新 分 支 上 举 个 例 子, 如 果 遇 到 前 面 提 到 的 有 人 推 送 了 经 过 变 基 的 提 交, 并 丢 弃 了 你 的 本 地 开 发 所 基 于 的 一 些 提 交 那 种 情 境, 如 果 我 们 不 是 执 行 合 并, 而 是 执 行 git rebase teamone/master, Git 将 会 : 检 查 哪 些 提 交 是 我 们 的 分 支 上 独 有 的 (C2,C3,C4,C6,C7) 检 查 其 中 哪 些 提 交 不 是 合 并 操 作 的 结 果 (C2,C3,C4) 检 查 哪 些 提 交 在 对 方 覆 盖 更 新 时 并 没 有 被 纳 入 目 标 分 支 ( 只 有 C2 和 C3, 因 为 C4 其 实 就 是 C4') 把 查 到 的 这 些 提 交 应 用 在 teamone/master 上 面 从 而 我 们 将 得 到 与 你 将 相 同 的 内 容 又 合 并 了 一 次, 生 成 了 一 个 新 的 提 交 中 不 同 的 结 果, 如 图 在 一 个 被 变 基 然 后 强 制 推 送 的 分 支 上 再 次 执 行 变 基 所 示

96 Figure 48. 在 一 个 被 变 基 然 后 强 制 推 送 的 分 支 上 再 次 执 行 变 基 要 想 上 述 方 案 有 效, 还 需 要 对 方 在 变 基 时 确 保 C4' 和 C4 是 几 乎 一 样 的 否 则 变 基 操 作 将 无 法 识 别, 并 新 建 另 一 个 类 似 C4 的 补 丁 ( 而 这 个 补 丁 很 可 能 无 法 整 洁 的 整 合 入 历 史, 因 为 补 丁 中 的 修 改 已 经 存 在 于 某 个 地 方 了 ) 在 本 例 中 另 一 种 简 单 的 方 法 是 使 用 git pull --rebase 命 令 而 不 是 直 接 git pull 又 或 者 你 可 以 自 己 手 动 完 成 这 个 过 程, 先 git fetch, 再 git rebase teamone/master 如 果 你 习 惯 使 用 git pull, 同 时 又 希 望 默 认 使 用 选 项 --rebase, 你 可 以 执 行 这 条 语 句 git config --global pull.rebase true 来 更 改 pull.rebase 的 默 认 配 置 只 要 你 把 变 基 命 令 当 作 是 在 推 送 前 清 理 提 交 使 之 整 洁 的 工 具, 并 且 只 在 从 未 推 送 至 共 用 仓 库 的 提 交 上 执 行 变 基 命 令, 你 就 不 会 有 事 假 如 你 在 那 些 已 经 被 推 送 至 共 用 仓 库 的 提 交 上 执 行 变 基 命 令, 并 因 此 丢 弃 了 一 些 别 人 的 开 发 所 基 于 的 提 交, 那 你 就 有 大 麻 烦 了, 你 的 同 事 也 会 因 此 鄙 视 你 如 果 你 或 你 的 同 事 在 某 些 情 形 下 决 意 要 这 么 做, 请 一 定 要 通 知 每 个 人 执 行 git pull --rebase 命 令, 这 样 尽 管 不 能 避 免 伤 痛, 但 能 有 所 缓 解 变 基 vs. 合 并 至 此, 你 已 在 实 战 中 学 习 了 变 基 和 合 并 的 用 法, 你 一 定 会 想 问, 到 底 哪 种 方 式 更 好 在 回 答 这 个 问 题 之 前, 让 我 们 退 后 一 步, 想 讨 论 一 下 提 交 历 史 到 底 意 味 着 什 么 有 一 种 观 点 认 为, 仓 库 的 提 交 历 史 即 是 记 录 实 际 发 生 过 什 么 它 是 针 对 历 史 的 文 档, 本 身 就 有 价 值, 不 能 乱 改 从 这 个 角 度 看 来, 改 变 提 交 历 史 是 一 种 亵 渎, 你 使 用 _ 谎 言 _ 掩 盖 了 实 际 发 生 过 的 事 情 如 果 由 合 并 产 生 的 提 交 历 史 是 一 团 糟 怎 么 办? 既 然 事 实 就 是 如 此, 那 么 这 些 痕 迹 就 应 该 被 保 留 下 来, 让 后 人 能 够 查 阅 另 一 种 观 点 则 正 好 相 反, 他 们 认 为 提 交 历 史 是 项 目 过 程 中 发 生 的 故 事 没 人 会 出 版 一 本 书 的 第 一 批 草 稿, 软 件

97 维 护 手 册 也 是 需 要 反 复 修 订 才 能 方 便 使 用 持 这 一 观 点 的 人 会 使 用 rebase 及 filter-branch 等 工 具 来 编 写 故 事, 怎 么 方 便 后 来 的 读 者 就 怎 么 写 现 在, 让 我 们 回 到 之 前 的 问 题 上 来, 到 底 合 并 还 是 变 基 好? 希 望 你 能 明 白, 并 没 有 一 个 简 单 的 答 案 Git 是 一 个 非 常 强 大 的 工 具, 它 允 许 你 对 提 交 历 史 做 许 多 事 情, 但 每 个 团 队 每 个 项 目 对 此 的 需 求 并 不 相 同 既 然 你 已 经 分 别 学 习 了 两 者 的 用 法, 相 信 你 能 够 根 据 实 际 情 况 作 出 明 智 的 选 择 总 的 原 则 是, 只 对 尚 未 推 送 或 分 享 给 别 人 的 本 地 修 改 执 行 变 基 操 作 清 理 历 史, 从 不 对 已 推 送 至 别 处 的 提 交 执 行 变 基 操 作, 这 样, 你 才 能 享 受 到 两 种 方 式 带 来 的 便 利 总 结 我 们 已 经 讲 完 了 Git 分 支 与 合 并 的 基 础 知 识 你 现 在 应 该 能 自 如 地 创 建 并 切 换 至 新 分 支 在 不 同 分 支 之 间 切 换 以 及 合 并 本 地 分 支 你 现 在 应 该 也 能 通 过 推 送 你 的 分 支 至 共 享 服 务 以 分 享 它 们 使 用 共 享 分 支 与 他 人 协 作 以 及 在 共 享 之 前 使 用 变 基 操 作 合 并 你 的 分 支 下 一 章, 我 们 将 要 讲 到, 如 果 你 想 要 运 行 自 己 的 Git 仓 库 托 管 服 务 器, 你 需 要 知 道 些 什 么

98 服 务 器 上 的 Git 到 目 前 为 止, 你 应 该 已 经 有 办 法 使 用 Git 来 完 成 日 常 工 作 然 而, 为 了 使 用 Git 协 作 功 能, 你 还 需 要 有 远 程 的 Git 仓 库 尽 管 在 技 术 上 你 可 以 从 个 人 仓 库 进 行 推 送 (push) 和 拉 取 (pull) 来 修 改 内 容, 但 不 鼓 励 使 用 这 种 方 法, 因 为 一 不 留 心 就 很 容 易 弄 混 其 他 人 的 进 度 此 外, 你 希 望 你 的 合 作 者 们 即 使 在 你 的 电 脑 未 联 机 时 亦 能 存 取 仓 库 拥 有 一 个 更 可 靠 的 公 用 仓 库 十 分 有 用 因 此, 与 他 人 合 作 的 最 佳 方 法 即 是 建 立 一 个 你 与 合 作 者 们 都 有 权 利 访 问, 且 可 从 那 里 推 送 和 拉 取 资 料 的 共 用 仓 库 架 设 一 台 Git 服 务 器 并 不 难 首 先, 选 择 你 希 望 服 务 器 使 用 的 通 讯 协 议 在 本 章 第 一 节 将 介 绍 可 用 的 协 议 以 及 各 自 优 缺 点 下 面 一 节 将 解 释 使 用 那 些 协 议 的 典 型 设 置 及 如 何 在 你 的 服 务 器 上 运 行 最 后, 如 果 你 不 介 意 托 管 你 的 代 码 在 其 他 人 的 服 务 器, 且 不 想 经 历 设 置 与 维 护 自 己 服 务 器 的 麻 烦, 可 以 试 试 我 们 介 绍 的 几 个 仓 库 托 管 服 务 如 果 你 对 架 设 自 己 的 服 务 器 没 兴 趣, 可 以 跳 到 本 章 最 后 一 节 去 看 看 如 何 申 请 一 个 代 码 托 管 服 务 的 帐 户 然 后 继 续 下 一 章, 我 们 会 在 那 里 讨 论 分 散 式 源 码 控 制 环 境 的 林 林 总 总 一 个 远 程 仓 库 通 常 只 是 一 个 裸 仓 库 (bare repository) 即 一 个 没 有 当 前 工 作 目 录 的 仓 库 因 为 该 仓 库 仅 仅 作 为 合 作 媒 介, 不 需 要 从 磁 碟 检 查 快 照 ; 存 放 的 只 有 Git 的 资 料 简 单 的 说, 裸 仓 库 就 是 你 专 案 目 录 内 的.git 子 目 录 内 容, 不 包 含 其 他 资 料 协 议 Git 可 以 使 用 四 种 主 要 的 协 议 来 传 输 资 料 : 本 地 协 议 (Local),HTTP 协 议,SSH(Secure Shell) 协 议 及 Git 协 议 在 此, 我 们 将 会 讨 论 那 些 协 议 及 哪 些 情 形 应 该 使 用 ( 或 避 免 使 用 ) 他 们 本 地 协 议 最 基 本 的 就 是 本 地 协 议 (Local protocol), 其 中 的 远 程 版 本 库 就 是 硬 盘 内 的 另 一 个 目 录 这 常 见 于 团 队 每 一 个 成 员 都 对 一 个 共 享 的 文 件 系 统 ( 例 如 一 个 挂 载 的 NFS) 拥 有 访 问 权, 或 者 比 较 少 见 的 多 人 共 用 同 一 台 电 脑 的 情 况 后 者 并 不 理 想, 因 为 你 的 所 有 代 码 版 本 库 如 果 长 存 于 同 一 台 电 脑, 更 可 能 发 生 灾 难 性 的 损 失 如 果 你 使 用 共 享 文 件 系 统, 就 可 以 从 本 地 版 本 库 克 隆 (clone) 推 送 (push) 以 及 拉 取 (pull) 像 这 样 去 克 隆 一 个 版 本 库 或 者 增 加 一 个 远 程 到 现 有 的 项 目 中, 使 用 版 本 库 路 径 作 为 URL 例 如, 克 隆 一 个 本 地 版 本 库, 可 以 执 行 如 下 的 命 令 : $ git clone /opt/git/project.git 或 你 可 以 执 行 这 个 命 令 : $ git clone file:///opt/git/project.git 如 果 在 URL 开 头 明 确 的 指 定 file://, 那 么 Git 的 行 为 会 略 有 不 同 如 果 仅 是 指 定 路 径,Git 会 尝 试 使 用 硬 链 接 (hard link) 或 直 接 复 制 所 需 要 的 文 件 如 果 指 定 file://,git 会 触 发 平 时 用 于 网 路 传 输 资 料 的 进 程, 那 通 常 是 传 输 效 率 较 低 的 方 法 指 定 file:// 的 主 要 目 的 是 取 得 一 个 没 有 外 部 参 考 (extraneous references) 或

99 对 象 (object) 的 干 净 版 本 库 副 本 通 常 是 在 从 其 他 版 本 控 制 系 统 导 入 后 或 一 些 类 似 情 况 ( 参 见 Git 内 部 原 理 for maintenance tasks) 需 要 这 么 做 在 此 我 们 将 使 用 普 通 路 径, 因 为 这 样 通 常 更 快 要 增 加 一 个 本 地 版 本 库 到 现 有 的 Git 项 目, 可 以 执 行 如 下 的 命 令 : $ git remote add local_proj /opt/git/project.git 然 后, 就 可 以 像 在 网 络 上 一 样 从 远 端 版 本 库 推 送 和 拉 取 更 新 了 优 点 基 于 文 件 系 统 的 版 本 库 的 优 点 是 简 单, 并 且 直 接 使 用 了 现 有 的 文 件 权 限 和 网 络 访 问 权 限 如 果 你 的 团 队 已 经 有 共 享 文 件 系 统, 建 立 版 本 库 会 十 分 容 易 只 需 要 像 设 置 其 他 共 享 目 录 一 样, 把 一 个 裸 版 本 库 的 副 本 放 到 大 家 都 可 以 访 问 的 路 径, 并 设 置 好 读 / 写 的 权 限, 就 可 以 了, 我 们 会 在 在 服 务 器 上 搭 建 Git 讨 论 如 何 导 出 一 个 裸 版 本 库 这 也 是 快 速 从 别 人 的 工 作 目 录 中 拉 取 更 新 的 方 法 如 果 你 和 别 人 一 起 合 作 一 个 项 目, 他 想 让 你 从 版 本 库 中 拉 取 更 新 时, 运 行 类 似 git pull /home/john/project 的 命 令 比 推 送 到 服 务 再 取 回 简 单 多 了 缺 点 这 种 方 法 的 缺 点 是, 通 常 共 享 文 件 系 统 比 较 难 配 置, 并 且 比 起 基 本 的 网 络 连 接 访 问, 这 不 方 便 从 多 个 位 置 访 问 如 果 你 想 从 家 里 推 送 内 容, 必 须 先 挂 载 一 个 远 程 磁 盘, 相 比 网 络 连 接 的 访 问 方 式, 配 置 不 方 便, 速 度 也 慢 值 得 一 提 的 是, 如 果 你 使 用 的 是 类 似 于 共 享 挂 载 的 文 件 系 统 时, 这 个 方 法 不 一 定 是 最 快 的 访 问 本 地 版 本 库 的 速 度 与 你 访 问 数 据 的 速 度 是 一 样 的 在 同 一 个 服 务 器 上, 如 果 允 许 Git 访 问 本 地 硬 盘, 一 般 的 通 过 NFS 访 问 版 本 库 要 比 通 过 SSH 访 问 慢 最 终, 这 个 协 议 并 不 保 护 仓 库 避 免 意 外 的 损 坏 每 一 个 用 户 都 有 远 程 目 录 的 完 整 shell 权 限, 没 有 方 法 可 以 阻 止 他 们 修 改 或 删 除 Git 内 部 文 件 和 损 坏 仓 库 HTTP 协 议 Git 通 过 HTTP 通 信 有 两 种 模 式 在 Git 版 本 之 前 只 有 一 个 方 式 可 用, 十 分 简 单 并 且 通 常 是 只 读 模 式 的 Git 版 本 引 入 了 一 种 新 的 更 智 能 的 协 议, 让 Git 可 以 像 通 过 SSH 那 样 智 能 的 协 商 和 传 输 数 据 之 后 几 年, 这 个 新 的 HTTP 协 议 因 为 其 简 单 智 能 变 的 十 分 流 行 新 版 本 的 HTTP 协 议 一 般 被 称 为 智 能 HTTP 协 议, 旧 版 本 的 一 般 被 称 为 哑 HTTP 协 议 我 们 先 了 解 一 下 新 的 智 能 HTTP 协 议 智 能 (Smart) HTTP 协 议 智 能 HTTP 协 议 的 运 行 方 式 和 SSH 及 Git 协 议 类 似, 只 是 运 行 在 标 准 的 HTTP/S 端 口 上 并 且 可 以 使 用 各 种 HTTP 验 证 机 制, 这 意 味 着 使 用 起 来 会 比 SSH 协 议 简 单 的 多, 比 如 可 以 使 用 HTTP 协 议 的 用 户 名 / 密 码 的 基 础 授 权, 免 去 设 置 SSH 公 钥 智 能 HTTP 协 议 或 许 已 经 是 最 流 行 的 使 用 Git 的 方 式 了, 它 即 支 持 像 git:// 协 议 一 样 设 置 匿 名 服 务, 也 可 以 像 SSH 协 议 一 样 提 供 传 输 时 的 授 权 和 加 密 而 且 只 用 一 个 URL 就 可 以 都 做 到, 省 去 了 为 不 同 的 需 求 设 置 不 同 的 URL 如 果 你 要 推 送 到 一 个 需 要 授 权 的 服 务 器 上 ( 一 般 来 讲 都 需 要 ), 服 务 器 会 提 示 你 输 入 用 户 名 和 密 码 从 服

100 务 器 获 取 数 据 时 也 一 样 事 实 上, 类 似 GitHub 的 服 务, 你 在 网 页 上 看 到 的 URL ( 比 如, 和 你 在 克 隆 推 送 ( 如 果 你 有 权 限 ) 时 使 用 的 是 一 样 的 哑 (Dumb) HTTP 协 议 如 果 服 务 器 没 有 提 供 智 能 HTTP 协 议 的 服 务,Git 客 户 端 会 尝 试 使 用 更 简 单 的 哑 HTTP 协 议 哑 HTTP 协 议 里 web 服 务 器 仅 把 裸 版 本 库 当 作 普 通 文 件 来 对 待, 提 供 文 件 服 务 哑 HTTP 协 议 的 优 美 之 处 在 于 设 置 起 来 简 单 基 本 上, 只 需 要 把 一 个 裸 版 本 库 放 在 HTTP 跟 目 录, 设 置 一 个 叫 做 post-update 的 挂 钩 就 可 以 了 ( 见 Git 钩 子 ) 此 时, 只 要 能 访 问 web 服 务 器 上 你 的 版 本 库, 就 可 以 克 隆 你 的 版 本 库 下 面 是 设 置 从 HTTP 访 问 版 本 库 的 方 法 : $ cd /var/www/htdocs/ $ git clone --bare /path/to/git_project gitproject.git $ cd gitproject.git $ mv hooks/post-update.sample hooks/post-update $ chmod a+x hooks/post-update 这 样 就 可 以 了 Git 自 带 的 post-update 挂 钩 会 默 认 执 行 合 适 的 命 令 (git update-server-info), 来 确 保 通 过 HTTP 的 获 取 和 克 隆 操 作 正 常 工 作 这 条 命 令 会 在 你 通 过 SSH 向 版 本 库 推 送 之 后 被 执 行 ; 然 后 别 人 就 可 以 通 过 类 似 下 面 的 命 令 来 克 隆 : $ git clone 这 里 我 们 用 了 Apache 里 设 置 了 常 用 的 路 径 /var/www/htdocs, 不 过 你 可 以 使 用 任 何 静 态 web 服 务 器 只 需 要 把 裸 版 本 库 放 到 正 确 的 目 录 下 就 可 以 Git 的 数 据 是 以 基 本 的 静 态 文 件 形 式 提 供 的 ( 详 情 见 Git 内 部 原 理 ) 通 常 的, 会 在 可 以 提 供 读 / 写 的 智 能 HTTP 服 务 和 简 单 的 只 读 的 哑 HTTP 服 务 之 间 选 一 个 极 少 会 将 二 者 混 合 提 供 服 务 优 点 我 们 将 只 关 注 智 能 HTTP 协 议 的 优 点 不 同 的 访 问 方 式 只 需 要 一 个 URL 以 及 服 务 器 只 在 需 要 授 权 时 提 示 输 入 授 权 信 息, 这 两 个 简 便 性 让 终 端 用 户 使 用 Git 变 得 非 常 简 单 相 比 SSH 协 议, 可 以 使 用 用 户 名 / 密 码 授 权 是 一 个 很 大 的 优 势, 这 样 用 户 就 不 必 须 在 使 用 Git 之 前 先 在 本 地 生 成 SSH 密 钥 对 再 把 公 钥 上 传 到 服 务 器 对 非 资 深 的 使 用 者, 或 者 系 统 上 缺 少 SSH 相 关 程 序 的 使 用 者,HTTP 协 议 的 可 用 性 是 主 要 的 优 势 与 SSH 协 议 类 似,HTTP 协 议 也 非 常 快 和 高 效 你 也 可 以 在 HTTPS 协 议 上 提 供 只 读 版 本 库 的 服 务, 如 此 你 在 传 输 数 据 的 时 候 就 可 以 加 密 数 据 ; 或 者, 你 甚 至 可 以 让 客 户 端 使 用 指 定 的 SSL 证 书

101 另 一 个 好 处 是 HTTP/S 协 议 被 广 泛 使 用, 一 般 的 企 业 防 火 墙 都 会 允 许 这 些 端 口 的 数 据 通 过 缺 点 在 一 些 服 务 器 上, 架 设 HTTP/S 协 议 的 服 务 端 会 比 SSH 协 议 的 棘 手 一 些 除 了 这 一 点, 用 其 他 协 议 提 供 Git 服 务 与 智 能 HTTP 协 议 相 比 就 几 乎 没 有 优 势 了 如 果 你 在 HTTP 上 使 用 需 授 权 的 推 送, 管 理 凭 证 会 比 使 用 SSH 密 钥 认 证 麻 烦 一 些 然 而, 你 可 以 选 择 使 用 凭 证 存 储 工 具, 比 如 OSX 的 Keychain 或 者 Windows 的 凭 证 管 理 器 参 考 凭 证 存 储 如 何 安 全 地 保 存 HTTP 密 码 SSH 协 议 架 设 Git 服 务 器 时 常 用 SSH 协 议 作 为 传 输 协 议 因 为 大 多 数 环 境 下 已 经 支 持 通 过 SSH 访 问 即 时 没 有 也 比 较 很 容 易 架 设 SSH 协 议 也 是 一 个 验 证 授 权 的 网 络 协 议 ; 并 且, 因 为 其 普 遍 性, 架 设 和 使 用 都 很 容 易 通 过 SSH 协 议 克 隆 版 本 库, 你 可 以 指 定 一 个 ssh:// 的 URL: $ git clone ssh://user@server/project.git 或 者 使 用 一 个 简 短 的 scp 式 的 写 法 : $ git clone user@server:project.git 你 也 可 以 不 指 定 用 户,Git 会 使 用 当 前 登 录 的 用 户 名 优 势 用 SSH 协 议 的 优 势 有 很 多 首 先,SSH 架 设 相 对 简 单 SSH 守 护 进 程 很 常 见, 多 数 管 理 员 都 有 使 用 经 验, 并 且 多 数 操 作 系 统 都 包 含 了 它 及 相 关 的 管 理 工 具 其 次, 通 过 SSH 访 问 是 安 全 的 所 有 传 输 数 据 都 要 经 过 授 权 和 加 密 最 后, 与 HTTP/S 协 议 Git 协 议 及 本 地 协 议 一 样,SSH 协 议 很 高 效, 在 传 输 前 也 会 尽 量 压 缩 数 据 缺 点 SSH 协 议 的 缺 点 在 于 你 不 能 通 过 他 实 现 匿 名 访 问 即 便 只 要 读 取 数 据, 使 用 者 也 要 有 通 过 SSH 访 问 你 的 主 机 的 权 限, 这 使 得 SSH 协 议 不 利 于 开 源 的 项 目 如 果 你 只 在 公 司 网 络 使 用,SSH 协 议 可 能 是 你 唯 一 要 用 到 的 协 议 如 果 你 要 同 时 提 供 匿 名 只 读 访 问 和 SSH 协 议, 那 么 你 除 了 为 自 己 推 送 架 设 SSH 服 务 以 外, 还 得 架 设 一 个 可 以 让 其 他 人 访 问 的 服 务 Git 协 议 接 下 来 是 Git 协 议 这 是 包 含 在 Git 里 的 一 个 特 殊 的 守 护 进 程 ; 它 监 听 在 一 个 特 定 的 端 口 (9418), 类 似 于 SSH 服 务, 但 是 访 问 无 需 任 何 授 权 要 让 版 本 库 支 持 Git 协 议, 需 要 先 创 建 一 个 git-daemon-export-ok 文 件 它 是 Git 协 议 守 护 进 程 为 这 个 版 本 库 提 供 服 务 的 必 要 条 件 但 是 除 此 之 外 没 有 任 何 安 全 措 施 要 么 谁 都 可 以 克 隆 这 个 版 本 库, 要 么 谁 也 不 能 这 意 味 这, 通 常 不 能 通 过 Git 协 议 推 送 由 于 没 有 授 权 机 制, 一 旦 你 开 放

102 推 送 操 作, 意 味 着 网 络 上 知 道 这 个 项 目 URL 的 人 都 可 以 向 项 目 推 送 数 据 不 用 说, 极 少 会 有 人 这 么 做 优 点 目 前,Git 协 议 是 Git 使 用 的 网 络 传 输 协 议 里 最 快 的 如 果 你 的 项 目 有 很 大 的 访 问 量, 或 者 你 的 项 目 很 庞 大 并 且 不 需 要 为 写 进 行 用 户 授 权, 架 设 Git 守 护 进 程 来 提 供 服 务 是 不 错 的 选 择 它 使 用 与 SSH 相 同 的 数 据 传 输 机 制, 但 是 省 去 了 加 密 和 授 权 的 开 销 缺 点 Git 协 议 缺 点 是 缺 乏 授 权 机 制 把 Git 协 议 作 为 访 问 项 目 版 本 库 的 唯 一 手 段 是 不 可 取 的 一 般 的 做 法 里, 会 同 时 提 供 SSH 或 者 HTTPS 协 议 的 访 问 服 务, 只 让 少 数 几 个 开 发 者 有 推 送 ( 写 ) 权 限, 其 他 人 通 过 git:// 访 问 只 有 读 权 限 Git 协 议 也 许 也 是 最 难 架 设 的 它 要 求 有 自 己 的 守 护 进 程, 这 就 要 配 置 xinetd 或 者 其 他 的 程 序, 这 些 工 作 并 不 简 单 它 还 要 求 防 火 墙 开 放 9418 端 口, 但 是 企 业 防 火 墙 一 般 不 会 开 放 这 个 非 标 准 端 口 而 大 型 的 企 业 防 火 墙 通 常 会 封 锁 这 个 端 口 在 服 务 器 上 搭 建 Git 现 在 我 们 将 讨 论 如 何 在 你 自 己 的 服 务 器 上 搭 建 Git 服 务 来 运 行 这 些 协 议 NOTE 这 里 我 们 将 要 演 示 在 Linux 服 务 器 上 进 行 一 次 基 本 且 简 化 的 安 装 所 需 的 命 令 与 步 骤, 当 然 在 Mac 或 Windows 服 务 器 上 同 样 可 以 运 行 这 些 服 务 事 实 上, 在 你 的 计 算 机 基 础 架 构 中 建 立 一 个 生 产 环 境 服 务 器, 将 不 可 避 免 的 使 用 到 不 同 的 安 全 措 施 与 操 作 系 统 工 具 但 是, 希 望 你 能 从 本 节 中 获 得 一 些 必 要 的 知 识 在 开 始 架 设 Git 服 务 器 前, 需 要 把 现 有 仓 库 导 出 为 裸 仓 库 即 一 个 不 包 含 当 前 工 作 目 录 的 仓 库 这 通 常 是 很 简 单 的 为 了 通 过 克 隆 你 的 仓 库 来 创 建 一 个 新 的 裸 仓 库, 你 需 要 在 克 隆 命 令 后 加 上 `--bare` 选 项 按 照 惯 例, 裸 仓 库 目 录 名 以.git 结 尾, 就 像 这 样 : $ git clone --bare my_project my_project.git Cloning into bare repository 'my_project.git'... done. 现 在, 你 的 my_project.git 目 录 中 应 该 有 Git 目 录 的 副 本 了 整 体 上 效 果 大 致 相 当 于 $ cp -Rf my_project/.git my_project.git 虽 然 在 配 置 文 件 中 有 若 干 不 同, 但 是 对 于 你 的 目 的 来 说, 这 两 种 方 式 都 是 一 样 的 它 只 取 出 Git 仓 库 自 身, 不 要 工 作 目 录, 然 后 特 别 为 它 单 独 创 建 一 个 目 录

103 把 裸 仓 库 放 到 服 务 器 上 既 然 你 有 了 裸 仓 库 的 副 本, 剩 下 要 做 的 就 是 把 裸 仓 库 放 到 服 务 器 上 并 设 置 你 的 协 议 假 设 一 个 域 名 为 git.example.com 的 服 务 器 已 经 架 设 好, 并 可 以 通 过 SSH 连 接, 你 想 把 所 有 的 Git 仓 库 放 在 /opt/git 目 录 下 假 设 服 务 器 上 存 在 /opt/git/ 目 录, 你 可 以 通 过 以 下 命 令 复 制 你 的 裸 仓 库 来 创 建 一 个 新 仓 库 : $ scp -r my_project.git user@git.example.com:/opt/git 此 时, 其 他 通 过 SSH 连 接 这 台 服 务 器 并 对 /opt/git 目 录 拥 有 可 读 权 限 的 使 用 者, 通 过 运 行 以 下 命 令 就 可 以 克 隆 你 的 仓 库 $ git clone user@git.example.com:/opt/git/my_project.git 如 果 一 个 用 户, 通 过 使 用 SSH 连 接 到 一 个 服 务 器, 并 且 其 对 /opt/git/my_project.git 目 录 拥 有 可 写 权 限, 那 么 他 将 自 动 拥 有 推 送 权 限 如 果 到 该 项 目 目 录 中 运 行 git init 命 令, 并 加 上 --shared 选 项, 那 么 Git 会 自 动 修 改 该 仓 库 目 录 的 组 权 限 为 可 写 $ ssh user@git.example.com $ cd /opt/git/my_project.git $ git init --bare --shared 由 此 可 见, 根 据 现 有 的 Git 仓 库 创 建 一 个 裸 仓 库, 然 后 把 它 放 上 你 和 协 作 者 都 有 SSH 访 问 权 的 服 务 器 是 多 么 容 易 现 在 你 们 已 经 准 备 好 在 同 一 项 目 上 展 开 合 作 了 值 得 注 意 的 是, 这 的 确 是 架 设 一 个 几 个 人 拥 有 连 接 权 的 Git 服 务 的 全 部 只 要 在 服 务 器 上 加 入 可 以 用 SSH 登 录 的 帐 号, 然 后 把 裸 仓 库 放 在 大 家 都 有 读 写 权 限 的 地 方 你 已 经 准 备 好 了 一 切, 无 需 更 多 下 面 的 几 节 中, 你 会 了 解 如 何 扩 展 到 更 复 杂 的 设 定 这 些 内 容 包 含 如 何 避 免 为 每 一 个 用 户 建 立 一 个 账 户, 给 仓 库 添 加 公 共 读 取 权 限, 架 设 网 页 界 面 等 等 然 而, 请 记 住 这 一 点, 如 果 只 是 和 几 个 人 在 一 个 私 有 项 目 上 合 作 的 话, 仅 仅 是 一 个 SSH 服 务 器 和 裸 仓 库 就 足 够 了 小 型 安 装 如 果 设 备 较 少 或 者 你 只 想 在 小 型 开 发 团 队 里 尝 试 Git, 那 么 一 切 都 很 简 单 架 设 Git 服 务 最 复 杂 的 地 方 在 于 用 户 管 理 如 果 需 要 仓 库 对 特 定 的 用 户 可 读, 而 给 另 一 部 分 用 户 读 写 权 限, 那 么 访 问 和 许 可 安 排 就 会 比 较 困 难 SSH 连 接 如 果 你 有 一 台 所 有 开 发 者 都 可 以 用 SSH 连 接 的 服 务 器, 架 设 你 的 第 一 个 仓 库 就 十 分 简 单 了, 因 为 你 几 乎 什 么 都 不 用 做 ( 正 如 我 们 上 一 节 所 说 的 ) 如 果 你 想 在 你 的 仓 库 上 设 置 更 复 杂 的 访 问 控 制 权 限, 只 要 使 用 服 务 器 操 作 系 统 的 普 通 的 文 件 系 统 权 限 就 行 了

104 如 果 需 要 团 队 里 的 每 个 人 都 对 仓 库 有 写 权 限, 又 不 能 给 每 个 人 在 服 务 器 上 建 立 账 户, 那 么 提 供 SSH 连 接 就 是 唯 一 的 选 择 了 我 们 假 设 用 来 共 享 仓 库 的 服 务 器 已 经 安 装 了 SSH 服 务, 而 且 你 通 过 它 访 问 服 务 器 有 几 个 方 法 可 以 使 你 给 团 队 每 个 成 员 提 供 访 问 权 第 一 个 就 是 给 团 队 里 的 每 个 人 创 建 账 号, 这 种 方 法 很 直 接 但 也 很 麻 烦 或 许 你 不 会 想 要 为 每 个 人 运 行 一 次 adduser 并 且 设 置 临 时 密 码 第 二 个 办 法 是 在 主 机 上 建 立 一 个 git 账 户, 让 每 个 需 要 写 权 限 的 人 发 送 一 个 SSH 公 钥, 然 后 将 其 加 入 git 账 户 的 ~/.ssh/authorized_keys 文 件 这 样 一 来, 所 有 人 都 将 通 过 git 账 户 访 问 主 机 这 一 点 也 不 会 影 响 提 交 的 数 据 访 问 主 机 用 的 身 份 不 会 影 响 提 交 对 象 的 提 交 者 信 息 另 一 个 办 法 是 让 SSH 服 务 器 通 过 某 个 LDAP 服 务, 或 者 其 他 已 经 设 定 好 的 集 中 授 权 机 制, 来 进 行 授 权 只 要 每 个 用 户 可 以 获 得 主 机 的 shell 访 问 权 限, 任 何 SSH 授 权 机 制 你 都 可 视 为 是 有 效 的 生 成 SSH 公 钥 如 前 所 述, 许 多 Git 服 务 器 都 使 用 SSH 公 钥 进 行 认 证 为 了 向 Git 服 务 器 提 供 SSH 公 钥, 如 果 某 系 统 用 户 尚 未 拥 有 密 钥, 必 须 事 先 为 其 生 成 一 份 这 个 过 程 在 所 有 操 作 系 统 上 都 是 相 似 的 首 先, 你 需 要 确 认 自 己 是 否 已 经 拥 有 密 钥 默 认 情 况 下, 用 户 的 SSH 密 钥 存 储 在 其 ~/.ssh 目 录 下 进 入 该 目 录 并 列 出 其 中 内 容, 你 便 可 以 快 速 确 认 自 己 是 否 已 拥 有 密 钥 : $ cd ~/.ssh $ ls authorized_keys2 id_dsa config id_dsa.pub known_hosts 我 们 需 要 寻 找 一 对 以 id_dsa 或 id_rsa 命 名 的 文 件, 其 中 一 个 带 有.pub 扩 展 名.pub 文 件 是 你 的 公 钥, 另 一 个 则 是 私 钥 如 果 找 不 到 这 样 的 文 件 ( 或 者 根 本 没 有.ssh 目 录 ), 你 可 以 通 过 运 行 ssh-keygen 程 序 来 创 建 它 们 在 Linux/Mac 系 统 中,ssh-keygen 随 SSH 软 件 包 提 供 ; 在 Windows 上, 该 程 序 包 含 于 MSysGit 软 件 包 中 $ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/schacon/.ssh/id_rsa): Created directory '/home/schacon/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/schacon/.ssh/id_rsa. Your public key has been saved in /home/schacon/.ssh/id_rsa.pub. 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), 然 后 它 会 要 求 你 输 入 两 次 密 钥 口 令 如 果 你 不 想 在 使 用 密 钥 时 输 入 口 令, 将 其 留 空 即 可

105 现 在, 进 行 了 上 述 操 作 的 用 户 需 要 将 各 自 的 公 钥 发 送 给 任 意 一 个 Git 服 务 器 管 理 员 ( 假 设 服 务 器 正 在 使 用 基 于 公 钥 的 SSH 验 证 设 置 ) 他 们 所 要 做 的 就 是 复 制 各 自 的.pub 文 件 内 容, 并 将 其 通 过 邮 件 发 送 公 钥 看 起 来 是 这 样 的 : $ cat ~/.ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3 Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA t3faojoasncm1q9x5+3v0ww68/eifmb1zuufljqjkprrx88xypndvjynby6vw/pb0rwert/en mz+aw4ozpntpi89zpmvmluayrd2ce86z/il8b+gw3r3+1nkatmikjn2so1d01qratlmqvssbx NrRFi9wrf+M7Q== schacon@mylaptop.local 关 于 在 多 种 操 作 系 统 中 生 成 SSH 密 钥 的 更 深 入 教 程, 请 参 阅 GitHub 的 SSH 密 钥 指 南 配 置 服 务 器 我 们 来 看 看 如 何 配 置 服 务 器 端 的 SSH 访 问 本 例 中, 我 们 将 使 用 authorized_keys 方 法 来 对 用 户 进 行 认 证 同 时 我 们 假 设 你 使 用 的 操 作 系 统 是 标 准 的 Linux 发 行 版, 比 如 Ubuntu 首 先, 创 建 一 个 操 作 系 统 用 户 git, 并 为 其 建 立 一 个.ssh 目 录 $ sudo adduser git $ su git $ cd $ mkdir.ssh && chmod 700.ssh $ touch.ssh/authorized_keys && chmod 600.ssh/authorized_keys 接 着, 我 们 需 要 为 系 统 用 户 git 的 authorized_keys 文 件 添 加 一 些 开 发 者 SSH 公 钥 假 设 我 们 已 经 获 得 了 若 干 受 信 任 的 公 钥, 并 将 它 们 保 存 在 临 时 文 件 中 与 前 文 类 似, 这 些 公 钥 看 起 来 是 这 样 的 : $ cat /tmp/id_rsa.john.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4L ojg6rs6hpb09j9r/t17/x4lhja0f3fr1rp6kybrswj2athgw6hxlm9/5zytk6ztg3rpkk+4k Yjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS9Ez Sdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+myiv O7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2AYYgPq dav8jggjicuvax2t9va5 gsg-keypair 将 这 些 公 钥 加 入 系 统 用 户 git 的.ssh 目 录 下 authorized_keys 文 件 的 末 尾 :

106 $ cat /tmp/id_rsa.john.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.josie.pub >> ~/.ssh/authorized_keys $ cat /tmp/id_rsa.jessica.pub >> ~/.ssh/authorized_keys 现 在 我 们 来 为 开 发 者 新 建 一 个 空 仓 库 可 以 借 助 带 --bare 选 项 的 git init 命 令 来 做 到 这 一 点, 该 命 令 在 初 始 化 仓 库 时 不 会 创 建 工 作 目 录 : $ cd /opt/git $ mkdir project.git $ cd project.git $ git init --bare Initialized empty Git repository in /opt/git/project.git/ 接 着,John Josie 或 者 Jessica 中 的 任 意 一 人 可 以 将 他 们 项 目 的 最 初 版 本 推 送 到 这 个 仓 库 中, 他 只 需 将 此 仓 库 设 置 为 项 目 的 远 程 仓 库 并 向 其 推 送 分 支 请 注 意, 每 添 加 一 个 新 项 目, 都 需 要 有 人 登 录 服 务 器 取 得 shell, 并 创 建 一 个 裸 仓 库 我 们 假 定 这 个 设 置 了 git 用 户 和 Git 仓 库 的 服 务 器 使 用 gitserver 作 为 主 机 名 同 时, 假 设 该 服 务 器 运 行 在 内 网, 并 且 你 已 在 DNS 配 置 中 将 gitserver 指 向 此 服 务 器 那 么 我 们 可 以 运 行 如 下 命 令 ( 假 定 myproject 是 已 有 项 目 且 其 中 已 包 含 文 件 ): # on John's computer $ cd myproject $ git init $ git add. $ git commit -m 'initial commit' $ git remote add origin git@gitserver:/opt/git/project.git $ git push origin master 此 时, 其 他 开 发 者 可 以 克 隆 此 仓 库, 并 推 回 各 自 的 改 动, 步 骤 很 简 单 : $ git clone git@gitserver:/opt/git/project.git $ cd project $ vim README $ git commit -am 'fix for the README file' $ git push origin master 通 过 这 种 方 法, 你 可 以 快 速 搭 建 一 个 具 有 读 写 权 限 面 向 多 个 开 发 者 的 Git 服 务 器 需 要 注 意 的 是, 目 前 所 有 ( 获 得 授 权 的 ) 开 发 者 用 户 都 能 以 系 统 用 户 git 的 身 份 登 录 服 务 器 从 而 获 得 一 个 普 通 shell 如 果 你 想 对 此 加 以 限 制, 则 需 要 修 改 passwd 文 件 中 (git 用 户 所 对 应 ) 的 shell 值 借 助 一 个 名 为 git-shell 的 受 限 shell 工 具, 你 可 以 方 便 地 将 用 户 git 的 活 动 限 制 在 与 Git 相 关 的 范 围 内 该

107 工 具 随 Git 软 件 包 一 同 提 供 如 果 将 git-shell 设 置 为 用 户 git 的 登 录 shell(login shell), 那 么 用 户 git 便 不 能 获 得 此 服 务 器 的 普 通 shell 访 问 权 限 若 要 使 用 git-shell, 需 要 用 它 替 换 掉 bash 或 csh, 使 其 成 为 系 统 用 户 的 登 录 shell 为 进 行 上 述 操 作, 首 先 你 必 须 确 保 git-shell 已 存 在 于 /etc/shells 文 件 中 : $ 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 vim /etc/shells # and add the path to git-shell from last command 现 在 你 可 以 使 用 chsh <username> 命 令 修 改 任 一 系 统 用 户 的 shell: $ sudo chsh git # and enter the path to git-shell, usually: /usr/bin/gitshell 这 样, 用 户 git 就 只 能 利 用 SSH 连 接 对 Git 仓 库 进 行 推 送 和 拉 取 操 作, 而 不 能 登 录 机 器 并 取 得 普 通 shell 如 果 试 图 登 录, 你 会 发 现 尝 试 被 拒 绝, 像 这 样 : $ ssh git@gitserver fatal: Interactive git shell is not enabled. hint: ~/git-shell-commands should exist and have read and execute access. Connection to gitserver closed. 现 在, 网 络 相 关 的 Git 命 令 依 然 能 够 正 常 工 作, 但 是 开 发 者 用 户 已 经 无 法 得 到 一 个 普 通 shell 了 正 如 输 出 信 息 所 提 示 的, 你 也 可 以 在 git 用 户 的 家 目 录 下 建 立 一 个 目 录, 来 对 git-shell 命 令 进 行 一 定 程 度 的 自 定 义 比 如, 你 可 以 限 制 掉 某 些 本 应 被 服 务 器 接 受 的 Git 命 令, 或 者 对 刚 才 的 SSH 拒 绝 登 录 信 息 进 行 自 定 义, 这 样, 当 有 开 发 者 用 户 以 类 似 方 式 尝 试 登 录 时, 便 会 看 到 你 的 信 息 要 了 解 更 多 有 关 自 定 义 shell 的 信 息, 请 运 行 git help shell Git 守 护 进 程 接 下 来 我 们 将 通 过 Git 协 议 建 立 一 个 基 于 守 护 进 程 的 仓 库 对 于 快 速 且 无 需 授 权 的 Git 数 据 访 问, 这 是 一 个 理 想 之 选 请 注 意, 因 为 其 不 包 含 授 权 服 务, 任 何 通 过 该 协 议 管 理 的 内 容 将 在 其 网 络 上 公 开 如 果 运 行 在 防 火 墙 之 外 的 服 务 器 上, 它 应 该 只 对 那 些 公 开 的 只 读 项 目 服 务 如 果 运 行 在 防 火 墙 之 内 的 服 务 器 上, 它 可 用 于 支 撑 大 量 参 与 人 员 或 自 动 系 统 ( 用 于 持 续 集 成 或 编 译 的 主 机 ) 只 读 访 问 的 项 目, 这 样 可 以 省 去 逐 一 配 置 SSH 公 钥 的 麻 烦 无 论 何 时, 该 Git 协 议 都 是 相 对 容 易 设 定 的 通 常, 你 只 需 要 以 守 护 进 程 的 形 式 运 行 该 命 令 : git daemon --reuseaddr --base-path=/opt/git/ /opt/git/

108 --reuseaddr 允 许 服 务 器 在 无 需 等 待 旧 连 接 超 时 的 情 况 下 重 启,--base-path 选 项 允 许 用 户 在 未 完 全 指 定 路 径 的 条 件 下 克 隆 项 目, 结 尾 的 路 径 将 告 诉 Git 守 护 进 程 从 何 处 寻 找 仓 库 来 导 出 如 果 有 防 火 墙 正 在 运 行, 你 需 要 开 放 端 口 9418 的 通 信 权 限 你 可 以 通 过 许 多 方 式 将 该 进 程 以 守 护 进 程 的 方 式 运 行, 这 主 要 取 决 于 你 所 使 用 的 操 作 系 统 在 一 台 Ubuntu 机 器 上, 你 可 以 使 用 一 份 Upstart 脚 本 因 此, 找 到 如 下 文 件 : /etc/event.d/local-git-daemon 并 添 加 下 列 脚 本 内 容 : start on startup stop on shutdown exec /usr/bin/git daemon \ --user=git --group=git \ --reuseaddr \ --base-path=/opt/git/ \ /opt/git/ respawn 出 于 安 全 考 虑, 强 烈 建 议 使 用 一 个 对 仓 库 拥 有 只 读 权 限 的 用 户 身 份 来 运 行 该 守 护 进 程 - 你 可 以 创 建 一 个 新 用 户 git-ro 并 且 以 该 用 户 身 份 来 运 行 守 护 进 程 为 简 便 起 见, 我 们 将 像 git-shell 一 样, 同 样 使 用 git 用 户 来 运 行 它 当 你 重 启 机 器 时, 你 的 Git 守 护 进 程 将 会 自 动 启 动, 并 且 如 果 进 程 被 意 外 结 束 它 会 自 动 重 新 运 行 为 了 在 不 重 启 的 情 况 下 直 接 运 行, 你 可 以 运 行 以 下 命 令 : initctl start local-git-daemon 在 其 他 系 统 中, 你 可 以 使 用 sysvinit 系 统 中 的 xinetd 脚 本, 或 者 另 外 的 方 式 来 实 现 - 只 要 你 能 够 将 其 命 令 守 护 进 程 化 并 实 现 监 控 接 下 来, 你 需 要 告 诉 Git 哪 些 仓 库 允 许 基 于 服 务 器 的 无 授 权 访 问 你 可 以 在 每 个 仓 库 下 创 建 一 个 名 为 gitdaemon-export-ok 的 文 件 来 实 现 $ cd /path/to/project.git $ touch git-daemon-export-ok 该 文 件 将 允 许 Git 提 供 无 需 授 权 的 项 目 访 问 服 务

109 Smart HTTP 我 们 一 般 通 过 SSH 进 行 授 权 访 问, 通 过 git:// 进 行 无 授 权 访 问, 但 是 还 有 一 种 协 议 可 以 同 时 实 现 以 上 两 种 方 式 的 访 问 设 置 Smart HTTP 一 般 只 需 要 在 服 务 器 上 启 用 一 个 Git 自 带 的 名 为 git-http-backend 的 CGI 脚 本 该 CGI 脚 本 将 会 读 取 由 git fetch 或 git push 命 令 向 HTTP URL 发 送 的 请 求 路 径 和 头 部 信 息, 来 判 断 该 客 户 端 是 否 支 持 HTTP 通 信 ( 不 低 于 版 本 的 客 户 端 支 持 此 特 性 ) 如 果 CGI 发 现 该 客 户 端 支 持 智 能 (Smart) 模 式, 它 将 会 以 智 能 模 式 与 它 进 行 通 信, 否 则 它 将 会 回 落 到 哑 (Dumb) 模 式 下 ( 因 此 它 可 以 对 某 些 老 的 客 户 端 实 现 向 下 兼 容 ) 在 完 成 以 上 简 单 的 安 装 步 骤 后, 我 们 将 用 Apache 来 作 为 CGI 服 务 器 如 果 你 没 有 安 装 Apache, 你 可 以 在 Linux 环 境 下 执 行 如 下 或 类 似 的 命 令 来 安 装 : $ sudo apt-get install apache2 apache2-utils $ a2enmod cgi alias env 该 操 作 将 会 启 用 mod_cgi, mod_alias, 和 mod_env 等 Apache 模 块, 这 些 模 块 都 是 使 该 功 能 正 常 工 作 所 必 须 的 接 下 来 我 们 要 向 Apache 配 置 文 件 添 加 一 些 内 容, 来 让 git-http-backend 作 为 Web 服 务 器 对 /git 路 径 请 求 的 处 理 器 SetEnv GIT_PROJECT_ROOT /opt/git SetEnv GIT_HTTP_EXPORT_ALL ScriptAlias /git/ /usr/lib/git-core/git-http-backend/ 如 果 留 空 GIT_HTTP_EXPORT_ALL 这 个 环 境 变 量,Git 将 只 对 无 授 权 客 户 端 提 供 带 git-daemon-export-ok 文 件 的 版 本 库, 就 像 Git 守 护 进 程 一 样 接 着 你 需 要 让 Apache 接 受 通 过 该 路 径 的 请 求, 添 加 如 下 的 内 容 至 Apache 配 置 文 件 : <Directory "/usr/lib/git-core*"> Options ExecCGI Indexes Order allow,deny Allow from all Require all granted </Directory> 最 后, 如 果 想 实 现 写 操 作 授 权 验 证, 使 用 如 下 的 未 授 权 屏 蔽 配 置 即 可 :

110 <LocationMatch "^/git/.*/git-receive-pack$"> AuthType Basic AuthName "Git Access" AuthUserFile /opt/git/.htpasswd Require valid-user </LocationMatch> 这 需 要 你 创 建 一 个 包 含 所 有 合 法 用 户 密 码 的.htaccess 文 件 以 下 是 一 个 添 加 schacon 用 户 到 此 文 件 的 例 子 : $ htdigest -c /opt/git/.htpasswd "Git Access" schacon 你 可 以 通 过 许 多 方 式 添 加 Apache 授 权 用 户, 选 择 使 用 其 中 一 种 方 式 即 可 以 上 仅 仅 只 是 我 们 可 以 找 到 的 最 简 单 的 一 个 例 子 如 果 愿 意 的 话, 你 也 可 以 通 过 SSL 运 行 它, 以 保 证 所 有 数 据 是 在 加 密 状 态 下 进 行 传 输 的 我 们 不 想 深 入 去 讲 解 Apache 配 置 文 件, 因 为 你 可 能 会 使 用 不 同 的 Web 服 务 器, 或 者 可 能 有 不 同 的 授 权 需 求 它 的 主 要 原 理 是 使 用 一 个 Git 附 带 的, 名 为 git-http-backend 的 CGI 它 被 引 用 来 处 理 协 商 通 过 HTTP 发 送 和 接 收 的 数 据 它 本 身 并 不 包 含 任 何 授 权 功 能, 但 是 授 权 功 能 可 以 在 Web 服 务 器 层 引 用 它 时 被 轻 松 实 现 你 可 以 在 任 何 所 有 可 以 处 理 CGI 的 Web 服 务 器 上 办 到 这 点, 所 以 随 便 挑 一 个 你 最 熟 悉 的 Web 服 务 器 试 手 吧 NOTE 欲 了 解 更 多 的 有 关 配 置 Apache 授 权 访 问 的 信 息, 请 通 过 以 下 链 接 浏 览 Apache 文 档 : GitWeb 如 果 你 对 项 目 有 读 写 权 限 或 只 读 权 限, 你 可 能 需 要 建 立 起 一 个 基 于 网 页 的 简 易 查 看 器 Git 提 供 了 一 个 叫 做 GitWeb 的 CGI 脚 本 来 做 这 项 工 作

111 Figure 49. GitWeb 的 网 页 用 户 界 面 如 果 你 想 要 查 看 GitWeb 如 何 展 示 你 的 项 目, 并 且 在 服 务 器 上 安 装 了 轻 量 级 网 络 服 务 器 比 如 lighttpd 或 webrick, Git 提 供 了 一 个 命 令 来 让 你 启 动 一 个 临 时 的 服 务 器 在 Linux 系 统 的 电 脑 上,lighttpd 通 常 已 经 安 装 了, 所 以 你 只 需 要 在 项 目 目 录 里 执 行 git instaweb 命 令 即 可 如 果 你 使 用 Mac 系 统, Mac OS X Leopard 系 统 已 经 预 安 装 了 Ruby, 所 以 webrick 或 许 是 你 最 好 的 选 择 如 果 不 想 使 用 lighttpd 启 动 instaweb 命 令, 你 需 要 在 执 行 时 加 入 --httpd 参 数 $ git instaweb --httpd=webrick [ :02:21] INFO WEBrick [ :02:21] INFO ruby ( ) [universal-darwin9.0] 这 个 命 令 启 动 了 一 个 监 听 1234 端 口 的 HTTP 服 务 器, 并 且 自 动 打 开 了 浏 览 器 这 对 你 来 说 十 分 方 便 当 你 已 经 完 成 了 工 作 并 想 关 闭 这 个 服 务 器, 你 可 以 执 行 同 一 个 命 令, 并 加 上 --stop 选 项 : $ git instaweb --httpd=webrick --stop 如 果 你 现 在 想 为 你 的 团 队 或 你 托 管 的 开 源 项 目 持 续 的 运 行 这 个 页 面, 你 需 要 通 过 普 通 的 Web 服 务 器 来 设 置 CGI 脚 本 一 些 Linux 发 行 版 的 软 件 库 有 gitweb 包, 可 以 通 过 apt 或 yum 来 安 装, 你 可 以 先 试 试 接 下 来 我 们 来

112 快 速 的 了 解 一 下 如 何 手 动 安 装 GitWeb 首 先, 你 需 要 获 得 Git 的 源 代 码, 它 包 含 了 GitWeb, 并 可 以 生 成 自 定 义 的 CGI 脚 本 : $ git clone git://git.kernel.org/pub/scm/git/git.git $ cd git/ $ make GITWEB_PROJECTROOT="/opt/git" prefix=/usr gitweb SUBDIR gitweb SUBDIR../ make[2]: `GIT-VERSION-FILE' is up to date. GEN gitweb.cgi GEN static/gitweb.js $ sudo cp -Rf gitweb /var/www/ 需 要 注 意 的 是, 你 需 要 在 命 令 中 指 定 GITWEB_PROJECTROOT 变 量 来 让 程 序 知 道 你 的 Git 版 本 库 的 位 置 现 在, 你 需 要 在 Apache 中 使 用 这 个 CGI 脚 本, 你 需 要 为 此 添 加 一 个 虚 拟 主 机 : <VirtualHost *:80> ServerName gitserver DocumentRoot /var/www/gitweb <Directory /var/www/gitweb> Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch AllowOverride All order allow,deny Allow from all AddHandler cgi-script cgi DirectoryIndex gitweb.cgi </Directory> </VirtualHost> 再 次 提 醒,GitWeb 可 以 通 过 任 何 一 个 支 持 CGI 或 Perl 的 网 络 服 务 器 架 设 ; 如 果 你 需 要 的 话, 架 设 起 来 应 该 不 会 很 困 难 现 在, 你 可 以 访 问 在 线 查 看 你 的 版 本 库 GitLab 虽 然 GitWeb 相 当 简 单 但 如 果 你 正 在 寻 找 一 个 更 现 代, 功 能 更 全 的 Git 服 务 器, 这 里 有 几 个 开 源 的 解 决 方 案 可 供 你 选 择 安 装 因 为 GitLab 是 其 中 最 出 名 的 一 个, 我 们 将 它 作 为 示 例 并 讨 论 它 的 安 装 和 使 用 这 比 GitWeb 要 复 杂 的 多 并 且 需 要 更 多 的 维 护, 但 它 的 确 是 一 个 功 能 更 全 的 选 择 安 装 GitLab 是 一 个 数 据 库 支 持 的 web 应 用, 所 以 相 比 于 其 他 git 服 务 器, 它 的 安 装 过 程 涉 及 到 更 多 的 东 西 幸 运 的 是, 这 个 过 程 有 非 常 详 细 的 文 档 说 明 和 支 持 这 里 有 一 些 可 参 考 的 方 法 帮 你 安 装 GitLab 为 了 更 快 速 的 启 动 和 运 行, 你 可 以 下 载 虚 拟 机 镜 像 或 者 在

113 上 获 取 一 键 安 装 包, 同 时 调 整 配 置 使 之 符 合 你 特 定 的 环 境 Bitnami 的 一 个 优 点 在 于 它 的 登 录 界 面 ( 通 过 alt-&rarr 键 进 入 ;); 它 会 告 诉 你 安 装 好 的 GitLab 的 IP 地 址 以 及 默 认 的 用 户 名 和 密 码 Figure 50. Bitnami GitLab 虚 拟 机 登 录 界 面 无 论 如 何, 跟 着 GitLab 社 区 版 的 readme 文 件 一 步 步 来, 你 可 以 在 这 里 找 到 它 在 这 里 你 将 会 在 主 菜 单 中 找 到 安 装 GitLab 的 帮 助, 一 个 可 以 在 Digital Ocean 上 运 行 的 虚 拟 机, 以 及 RPM 和 DEB 包 ( 都 是 测 试 版 ) 这 里 还 有 非 官 方 的 引 导 让 GitLab 运 行 在 非 标 准 的 操 作 系 统 和 数 据 库 上, 一 个 全 手 动 的 安 装 脚 本, 以 及 许 多 其 他 的 话 题 管 理 GitLab 的 管 理 界 面 是 通 过 网 络 进 入 的 将 你 的 浏 览 器 转 到 已 经 安 装 GitLab 的 主 机 名 或 IP 地 址, 然 后 以 管 理 员 身 份 登 录 即 可 默 认 的 用 户 名 是 admin@local.host, 默 认 的 密 码 是 5iveL!fe( 你 会 得 到 类 似 请 登 录 后 尽 快 更 换 密 码 的 提 示 ) 登 录 后, 点 击 主 栏 上 方 靠 右 位 置 的 Admin area 图 标 进 行 管 理 Figure 51. GitLab 主 栏 的 Admin area 图 标 使 用 者 GitLab 上 的 用 户 指 的 是 对 应 协 作 者 的 帐 号 用 户 帐 号 没 有 很 多 复 杂 的 地 方, 主 要 是 包 含 登 录 数 据 的 用 户 信 息 集 合 每 一 个 用 户 账 号 都 有 一 个 命 名 空 间, 即 该 用 户 项 目 的 逻 辑 集 合 如 果 一 个 叫 jane 的 用 户 拥 有 一 个 名 称 是

114 project 的 项 目, 那 么 这 个 项 目 的 url 会 是 Figure 52. GitLab 用 户 管 理 界 面 移 除 一 个 用 户 有 两 种 方 法 屏 蔽 (Blocking) 一 个 用 户 阻 止 他 登 录 GitLab 实 例, 但 是 该 用 户 命 名 空 间 下 的 所 有 数 据 仍 然 会 被 保 存, 并 且 仍 可 以 通 过 该 用 户 提 交 对 应 的 登 录 邮 箱 链 接 回 他 的 个 人 信 息 页 而 另 一 方 面, 销 毁 (Destroying) 一 个 用 户, 会 彻 底 的 将 他 从 数 据 库 和 文 件 系 统 中 移 除 他 命 名 空 间 下 的 所 有 项 目 和 数 据 都 会 被 删 除, 拥 有 的 任 何 组 也 会 被 移 除 这 显 然 是 一 个 更 永 久 且 更 具 破 坏 力 的 行 为, 所 以 很 少 用 到 这 种 方 法 组 一 个 GitLab 的 组 是 一 些 项 目 的 集 合, 连 同 关 于 多 少 用 户 可 以 访 问 这 些 项 目 的 数 据 每 一 个 组 都 有 一 个 项 目 命 名 空 间 ( 与 用 户 一 样 ), 所 以 如 果 一 个 叫 training 的 组 拥 有 一 个 名 称 是 materials 的 项 目, 那 么 这 个 项 目 的 url 会 是

115 Figure 53. GitLab 组 管 理 界 面 每 一 个 组 都 有 许 多 用 户 与 之 关 联, 每 一 个 用 户 对 组 中 的 项 目 以 及 组 本 身 的 权 限 都 有 级 别 区 分 权 限 的 范 围 从 访 客 ( 仅 能 提 问 题 和 讨 论 ) 到 拥 有 者 ( 完 全 控 制 组 成 员 和 项 目 ) 权 限 的 种 类 太 多 以 至 于 难 以 在 这 里 一 一 列 举, 不 过 在 GitLab 的 管 理 界 面 上 有 帮 助 链 接 项 目 一 个 GitLab 的 项 目 相 当 于 git 的 版 本 库 每 一 个 项 目 都 属 于 一 个 用 户 或 者 一 个 组 的 单 个 命 名 空 间 如 果 这 个 项 目 属 于 一 个 用 户, 那 么 这 个 拥 有 者 对 所 有 可 以 获 取 这 个 项 目 的 人 拥 有 直 接 管 理 权 ; 如 果 这 个 项 目 属 于 一 个 组, 那 么 该 组 中 用 户 级 别 的 权 限 也 会 起 作 用 每 一 个 项 目 都 有 一 个 可 视 级 别, 控 制 着 谁 可 以 看 到 这 个 项 目 页 面 和 仓 库 如 果 一 个 项 目 是 私 有 的, 这 个 项 目 的 拥 有 者 必 须 明 确 授 权 从 而 使 特 定 的 用 户 可 以 访 问 一 个 内 部 的 项 目 可 以 被 所 有 登 录 的 人 看 到, 而 一 个 公 开 的 项 目 则 是 对 所 有 人 可 见 的 注 意, 这 种 控 制 既 包 括 git fetch 的 使 用 也 包 括 对 项 目 web 用 户 界 面 的 访 问 钩 子 GitLab 在 项 目 和 系 统 级 别 上 都 支 持 钩 子 程 序 对 任 意 级 别, 当 有 相 关 事 件 发 生 时,GitLab 的 服 务 器 会 执 行 一 个 包 含 描 述 性 JSON 数 据 的 HTTP 请 求 这 是 自 动 化 连 接 你 的 git 版 本 库 和 GitLab 实 例 到 其 他 的 开 发 工 具, 比 如 CI 服 务 器, 聊 天 室, 或 者 部 署 工 具 的 一 个 极 好 方 法 基 本 用 途 你 想 要 在 GitLab 做 的 第 一 件 事 就 是 建 立 一 个 新 项 目 这 通 过 点 击 工 具 栏 上 的 + 图 标 完 成 你 会 被 要 求 填 写 项 目 名 称, 也 就 是 这 个 项 目 所 属 的 命 名 空 间, 以 及 它 的 可 视 层 级 绝 大 多 数 的 设 定 并 不 是 永 久 的, 可 以 通 过 设 置 界 面 重 新 调 整 点 击 Create Project, 你 就 完 成 了 项 目 存 在 后, 你 可 能 会 想 将 它 与 本 地 的 Git 版 本 库 连 接 每 一 个 项 目 都 可 以 通 过 HTTPS 或 者 SSH 连 接, 任 意 两 者 都 可 以 被 用 来 配 置 远 程 Git 在 项 目 主 页 的 顶 栏 可 以 看 到 这 个 项 目 的 URLs 对 于 一 个 存 在 的 本 地 版 本 库, 这

116 个 命 令 将 会 向 主 机 位 置 添 加 一 个 叫 gitlab 的 远 程 仓 库 : $ git remote add gitlab 如 果 你 的 本 地 没 有 版 本 库 的 副 本, 你 可 以 这 样 做 : $ git clone web 用 户 界 面 提 供 了 几 个 有 用 的 获 取 版 本 库 信 息 的 网 页 每 一 个 项 目 的 主 页 都 显 示 了 最 近 的 活 动, 并 且 通 过 顶 部 的 链 接 可 以 使 你 浏 览 项 目 文 件 以 及 提 交 日 志 一 起 工 作 在 一 个 GitLab 项 目 上 一 起 工 作 的 最 简 单 方 法 就 是 赋 予 协 作 者 对 git 版 本 库 的 直 接 push 权 限 你 可 以 通 过 项 目 设 定 的 Members( 成 员 ) 部 分 向 一 个 项 目 添 加 写 作 者, 并 且 将 这 个 新 的 协 作 者 与 一 个 访 问 级 别 关 联 ( 不 同 的 访 问 级 别 在 组 中 已 简 单 讨 论 ) 通 过 赋 予 一 个 协 作 者 Developer( 开 发 者 ) 或 者 更 高 的 访 问 级 别, 这 个 用 户 就 可 以 毫 无 约 束 地 直 接 向 版 本 库 或 者 向 分 支 进 行 提 交 另 外 一 个 让 合 作 更 解 耦 的 方 法 就 是 使 用 合 并 请 求 它 的 优 点 在 于 让 任 何 能 够 看 到 这 个 项 目 的 协 作 者 在 被 管 控 的 情 况 下 对 这 个 项 目 作 出 贡 献 可 以 直 接 访 问 的 协 作 者 能 够 简 单 的 创 建 一 个 分 支, 向 这 个 分 支 进 行 提 交, 也 可 以 开 启 一 个 向 master 或 者 其 他 任 何 一 个 分 支 的 合 并 请 求 对 版 本 库 没 有 推 送 权 限 的 协 作 者 则 可 以 fork 这 个 版 本 库 ( 即 创 建 属 于 自 己 的 这 个 库 的 副 本 ), 向 那 个 副 本 进 行 提 交, 然 后 从 那 个 副 本 开 启 一 个 到 主 项 目 的 合 并 请 求 这 个 模 型 使 得 项 目 拥 有 者 完 全 控 制 着 向 版 本 库 的 提 交, 以 及 什 么 时 候 允 许 加 入 陌 生 协 作 者 的 贡 献 在 GitLab 中 合 并 请 求 和 问 题 是 一 个 长 久 讨 论 的 主 要 部 分 每 一 个 合 并 请 求 都 允 许 在 提 出 改 变 的 行 进 行 讨 论 ( 它 支 持 一 个 轻 量 级 的 代 码 审 查 ), 也 允 许 对 一 个 总 体 性 话 题 进 行 讨 论 两 者 都 可 以 被 分 配 给 用 户, 或 者 组 织 到 milestones( 里 程 碑 ) 界 面 这 个 部 分 主 要 聚 焦 于 在 GitLab 中 与 Git 相 关 的 特 性, 但 是 GitLab 作 为 一 个 成 熟 的 系 统, 它 提 供 了 许 多 其 他 产 品 来 帮 助 你 协 同 工 作, 例 如 项 目 wiki 与 系 统 维 护 工 具 GitLab 的 一 个 优 点 在 于, 服 务 器 设 置 和 运 行 以 后, 你 将 很 少 需 要 调 整 配 置 文 件 或 通 过 SSH 连 接 服 务 器 ; 绝 大 多 数 的 管 理 和 日 常 使 用 都 可 以 在 浏 览 器 界 面 中 完 成 第 三 方 托 管 的 选 择 如 果 不 想 设 立 自 己 的 Git 服 务 器, 你 可 以 选 择 将 你 的 Git 项 目 托 管 到 一 个 外 部 专 业 的 托 管 网 站 这 带 来 了 一 些 好 处 : 一 个 托 管 网 站 可 以 用 来 快 速 建 立 并 开 始 项 目, 且 无 需 进 行 服 务 器 维 护 和 监 控 工 作 即 使 你 在 内 部 设 立 并 且 运 行 了 自 己 的 服 务 器, 你 仍 然 可 以 把 你 的 开 源 代 码 托 管 在 公 共 托 管 网 站 - 这 通 常 更 有 助 于 开 源 社 区 来 发 现 和 帮 助 你 现 在, 有 非 常 多 的 托 管 供 你 选 择, 每 个 选 择 都 有 不 同 的 优 缺 点 欲 查 看 最 新 列 表, 请 浏 览 Git 维 基 的 GitHosting 页 面 我 们 会 在 GitHub 详 细 讲 解 GitHub, 作 为 目 前 最 大 的 Git 托 管 平 台, 你 很 可 能 需 要 与 托 管 在 GitHub 上 的 项 目 进 行 交 互, 而 且 你 也 很 可 能 并 不 想 去 设 立 你 自 己 的 Git 服 务 器

117 总 结 你 有 多 种 远 程 存 取 Git 仓 库 的 选 择 便 于 与 其 他 人 合 作 或 是 分 享 你 的 工 作 运 行 你 自 己 的 服 务 器 将 有 许 多 权 限 且 允 许 你 运 行 该 服 务 于 你 自 己 的 防 火 墙 内, 但 如 此 通 常 需 要 耗 费 你 大 量 的 时 间 去 设 置 与 维 护 服 务 器 如 果 你 放 置 你 的 资 料 于 托 管 服 务 器 内, 可 轻 易 的 设 置 与 维 护 ; 无 论 如 何, 你 必 须 能 够 保 存 你 的 代 码 在 其 他 服 务 器, 且 某 些 组 织 不 允 许 此 作 法 这 将 直 接 了 当 的 决 定 哪 个 作 法 或 组 合 的 方 式 较 适 合 你 或 你 的 组 织

118 分 布 式 Git 你 现 在 拥 有 了 一 个 远 程 Git 版 本 库, 能 为 所 有 开 发 者 共 享 代 码 提 供 服 务, 在 一 个 本 地 工 作 流 程 下, 你 也 已 经 熟 悉 了 基 本 Git 命 令 你 现 在 可 以 学 习 如 何 利 用 Git 提 供 的 一 些 分 布 式 工 作 流 程 了 这 一 章 中, 你 将 会 学 习 如 何 作 为 贡 献 者 或 整 合 者, 在 一 个 分 布 式 协 作 的 环 境 中 使 用 Git 你 会 学 习 为 一 个 项 目 成 功 地 贡 献 代 码, 并 接 触 一 些 最 佳 实 践 方 式, 让 你 和 项 目 的 维 护 者 能 轻 松 地 完 成 这 个 过 程 另 外, 你 也 会 学 到 如 何 管 理 有 很 多 开 发 者 提 交 贡 献 的 项 目 分 布 式 工 作 流 程 同 传 统 的 集 中 式 版 本 控 制 系 统 (CVCS) 不 同,Git 的 分 布 式 特 性 使 得 开 发 者 间 的 协 作 变 得 更 加 灵 活 多 样 在 集 中 式 系 统 中, 每 个 开 发 者 就 像 是 连 接 在 集 线 器 上 的 节 点, 彼 此 的 工 作 方 式 大 体 相 像 而 在 Git 中, 每 个 开 发 者 同 时 扮 演 着 节 点 和 集 线 器 的 角 色 也 就 是 说, 每 个 开 发 者 既 可 以 将 自 己 的 代 码 贡 献 到 其 他 的 仓 库 中, 同 时 也 能 维 护 自 己 的 公 开 仓 库, 让 其 他 人 可 以 在 其 基 础 上 工 作 并 贡 献 代 码 由 此,Git 的 分 布 式 协 作 可 以 为 你 的 项 目 和 团 队 衍 生 出 种 种 不 同 的 工 作 流 程, 接 下 来 的 章 节 会 介 绍 几 种 利 用 了 Git 的 这 种 灵 活 性 的 常 见 应 用 方 式 我 们 将 讨 论 每 种 方 式 的 优 点 以 及 可 能 的 缺 点 ; 你 可 以 选 择 使 用 其 中 的 某 一 种, 或 者 将 它 们 的 特 性 混 合 搭 配 使 用 集 中 式 工 作 流 集 中 式 系 统 中 通 常 使 用 的 是 单 点 协 作 模 型 集 中 式 工 作 流 一 个 中 心 集 线 器, 或 者 说 仓 库, 可 以 接 受 代 码, 所 有 人 将 自 己 的 工 作 与 之 同 步 若 干 个 开 发 者 则 作 为 节 点 也 就 是 中 心 仓 库 的 消 费 者 并 且 与 其 进 行 同 步 Figure 54. 集 中 式 工 作 流 这 意 味 着 如 果 两 个 开 发 者 从 中 心 仓 库 克 隆 代 码 下 来, 同 时 作 了 一 些 修 改, 那 么 只 有 第 一 个 开 发 者 可 以 顺 利 地 把 数 据 推 送 回 共 享 服 务 器 第 二 个 开 发 者 在 推 送 修 改 之 前, 必 须 先 将 第 一 个 人 的 工 作 合 并 进 来, 这 样 才 不 会 覆 盖 第 一 个 人 的 修 改 这 和 Subversion ( 或 任 何 CVCS) 中 的 概 念 一 样, 而 且 这 个 模 式 也 可 以 很 好 地 运 用 到 Git 中 如 果 在 公 司 或 者 团 队 中, 你 已 经 习 惯 了 使 用 这 种 集 中 式 工 作 流 程, 完 全 可 以 继 续 采 用 这 种 简 单 的 模 式 只 需 要 搭 建 好 一 个 中 心 仓 库, 并 给 开 发 团 队 中 的 每 个 人 推 送 数 据 的 权 限, 就 可 以 开 展 工 作 了 Git 不 会 让 用 户 覆 盖 彼 此 的

119 修 改 例 如 John 和 Jessica 同 时 开 始 工 作 John 完 成 了 他 的 修 改 并 推 送 到 服 务 器 接 着 Jessica 尝 试 提 交 她 自 己 的 修 改, 却 遭 到 服 务 器 拒 绝 她 被 告 知 她 的 修 改 正 通 过 非 快 进 式 (non-fast-forward) 的 方 式 推 送, 只 有 将 数 据 抓 取 下 来 并 且 合 并 后 方 能 推 送 这 种 模 式 的 工 作 流 程 的 使 用 非 常 广 泛, 因 为 大 多 数 人 对 其 很 熟 悉 也 很 习 惯 当 然 这 并 不 局 限 于 小 团 队 利 用 Git 的 分 支 模 型, 通 过 同 时 在 多 个 分 支 上 工 作 的 方 式, 即 使 是 上 百 人 的 开 发 团 队 也 可 以 很 好 地 在 单 个 项 目 上 协 作 集 成 管 理 者 工 作 流 Git 允 许 多 个 远 程 仓 库 存 在, 使 得 这 样 一 种 工 作 流 成 为 可 能 : 每 个 开 发 者 拥 有 自 己 仓 库 的 写 权 限 和 其 他 所 有 人 仓 库 的 读 权 限 这 种 情 形 下 通 常 会 有 个 代 表 ` 官 方 ' 项 目 的 权 威 的 仓 库 要 为 这 个 项 目 做 贡 献, 你 需 要 从 该 项 目 克 隆 出 一 个 自 己 的 公 开 仓 库, 然 后 将 自 己 的 修 改 推 送 上 去 接 着 你 可 以 请 求 官 方 仓 库 的 维 护 者 拉 取 更 新 合 并 到 主 项 目 维 护 者 可 以 将 你 的 仓 库 作 为 远 程 仓 库 添 加 进 来, 在 本 地 测 试 你 的 变 更, 将 其 合 并 入 他 们 的 分 支 并 推 送 回 官 方 仓 库 这 一 流 程 的 工 作 方 式 如 下 所 示 ( 见 集 成 管 理 者 工 作 流 ): 1. 项 目 维 护 者 推 送 到 主 仓 库 2. 贡 献 者 克 隆 此 仓 库, 做 出 修 改 3. 贡 献 者 将 数 据 推 送 到 自 己 的 公 开 仓 库 4. 贡 献 者 给 维 护 者 发 送 邮 件, 请 求 拉 取 自 己 的 更 新 5. 维 护 者 在 自 己 本 地 的 仓 库 中, 将 贡 献 者 的 仓 库 加 为 远 程 仓 库 并 合 并 修 改 6. 维 护 者 将 合 并 后 的 修 改 推 送 到 主 仓 库 Figure 55. 集 成 管 理 者 工 作 流 这 是 GitHub 和 GitLab 等 集 线 器 式 (hub-based) 工 具 最 常 用 的 工 作 流 程 人 们 可 以 容 易 地 将 某 个 项 目 派 生 成 为 自 己 的 公 开 仓 库, 向 这 个 仓 库 推 送 自 己 的 修 改, 并 为 每 个 人 所 见 这 么 做 最 主 要 的 优 点 之 一 是 你 可 以 持 续 地 工 作, 而 主 仓 库 的 维 护 者 可 以 随 时 拉 取 你 的 修 改 贡 献 者 不 必 等 待 维 护 者 处 理 完 提 交 的 更 新 每 一 方 都 可 以 按 照 自 己 节 奏 工 作

120 司 令 官 与 副 官 工 作 流 这 其 实 是 多 仓 库 工 作 流 程 的 变 种 一 般 拥 有 数 百 位 协 作 开 发 者 的 超 大 型 项 目 才 会 用 到 这 样 的 工 作 方 式, 例 如 著 名 的 Linux 内 核 项 目 被 称 为 副 官 (lieutenant) 的 各 个 集 成 管 理 者 分 别 负 责 集 成 项 目 中 的 特 定 部 分 所 有 这 些 副 官 头 上 还 有 一 位 称 为 司 令 官 (dictator) 的 总 集 成 管 理 者 负 责 统 筹 司 令 官 维 护 的 仓 库 作 为 参 考 仓 库, 为 所 有 协 作 者 提 供 他 们 需 要 拉 取 的 项 目 代 码 整 个 流 程 看 起 来 是 这 样 的 ( 见 司 令 官 与 副 官 工 作 流 ): 1. 普 通 开 发 者 在 自 己 的 特 性 分 支 上 工 作, 并 根 据 master 分 支 进 行 变 基 这 里 是 司 令 官 的 `master` 分 支 2. 副 官 将 普 通 开 发 者 的 特 性 分 支 合 并 到 自 己 的 master 分 支 中 3. 司 令 官 将 所 有 副 官 的 master 分 支 并 入 自 己 的 master 分 支 中 4. 司 令 官 将 集 成 后 的 master 分 支 推 送 到 参 考 仓 库 中, 以 便 所 有 其 他 开 发 者 以 此 为 基 础 进 行 变 基 Figure 56. 司 令 官 与 副 官 工 作 流 这 种 工 作 流 程 并 不 常 用, 只 有 当 项 目 极 为 庞 杂, 或 者 需 要 多 级 别 管 理 时, 才 会 体 现 出 优 势 利 用 这 种 方 式, 项 目 总 负 责 人 ( 即 司 令 官 ) 可 以 把 大 量 分 散 的 集 成 工 作 委 托 给 不 同 的 小 组 负 责 人 分 别 处 理, 然 后 在 不 同 时 刻 将 大 块 的 代 码 子 集 统 筹 起 来, 用 于 之 后 的 整 合 工 作 流 程 总 结 上 面 介 绍 了 在 Git 等 分 布 式 系 统 中 经 常 使 用 的 工 作 流 程, 但 是 在 实 际 的 开 发 中, 你 会 遇 到 许 多 可 能 适 合 你 的 特 定 工 作 流 程 的 变 种 现 在 你 应 该 已 经 清 楚 哪 种 工 作 流 程 组 合 可 能 比 较 适 合 你 了, 我 们 会 给 出 一 些 如 何 扮 演 不 同 工 作 流 程 中 主 要 角 色 的 更 具 体 的 例 子 下 一 节 我 们 将 会 学 习 为 项 目 做 贡 献 的 一 些 常 用 模 式

121 向 一 个 项 目 贡 献 描 述 如 何 向 一 个 项 目 贡 献 的 主 要 困 难 在 于 完 成 贡 献 有 很 多 不 同 的 方 式 因 为 Git 非 常 灵 活, 人 们 可 以 通 过 不 同 的 方 式 来 一 起 工 作, 所 以 描 述 应 该 如 何 贡 献 并 不 是 非 常 准 确 - 每 一 个 项 目 都 有 一 点 儿 不 同 影 响 因 素 包 括 活 跃 贡 献 者 的 数 量 选 择 的 工 作 流 程 提 交 权 限 与 可 能 包 含 的 外 部 贡 献 方 法 第 一 个 影 响 因 素 是 活 跃 贡 献 者 的 数 量 - 积 极 地 向 这 个 项 目 贡 献 代 码 的 用 户 数 量 以 及 他 们 的 贡 献 频 率 在 许 多 情 况 下, 你 可 能 会 有 两 三 个 开 发 者 一 天 提 交 几 次, 对 于 不 活 跃 的 项 目 可 能 更 少 对 于 大 一 些 的 公 司 或 项 目, 开 发 者 的 数 量 可 能 会 是 上 千, 每 天 都 有 成 百 上 千 次 提 交 这 很 重 要, 因 为 随 着 开 发 者 越 来 越 多, 在 确 保 你 的 代 码 能 干 净 地 应 用 或 轻 松 地 合 并 时 会 遇 到 更 多 问 题 提 交 的 改 动 可 能 表 现 为 过 时 的, 也 可 能 在 你 正 在 做 改 动 或 者 等 待 改 动 被 批 准 应 用 时 被 合 并 入 的 工 作 严 重 损 坏 如 何 保 证 代 码 始 终 是 最 新 的, 并 且 提 交 始 终 是 有 效 的? 下 一 个 影 响 因 素 是 项 目 使 用 的 工 作 流 程 它 是 中 心 化 的 吗, 即 每 一 个 开 发 者 都 对 主 线 代 码 有 相 同 的 写 入 权 限? 项 目 是 否 有 一 个 检 查 所 有 补 丁 的 维 护 者 或 整 合 者? 是 否 所 有 的 补 丁 是 同 行 评 审 后 批 准 的? 你 是 否 参 与 了 那 个 过 程? 是 否 存 在 副 官 系 统, 你 必 须 先 将 你 的 工 作 提 交 到 上 面? 下 一 个 问 题 是 提 交 权 限 是 否 有 项 目 的 写 权 限 会 使 向 项 目 贡 献 所 需 的 流 程 有 极 大 的 不 同 如 果 没 有 写 权 限, 项 目 会 选 择 何 种 方 式 接 受 贡 献 的 工 作? 是 否 甚 至 有 一 个 如 何 贡 献 的 规 范? 你 一 次 贡 献 多 少 工 作? 你 多 久 贡 献 一 次? 所 有 这 些 问 题 都 会 影 响 实 际 如 何 向 一 个 项 目 贡 献, 以 及 对 你 来 说 哪 些 工 作 流 程 更 适 合 或 者 可 用 我 们 将 会 由 浅 入 深, 通 过 一 系 列 用 例 来 讲 述 其 中 的 每 一 个 方 面 ; 从 这 些 例 子 应 该 能 够 建 立 实 际 中 你 需 要 的 特 定 工 作 流 程 提 交 准 则 在 我 们 开 始 查 看 特 定 的 用 例 前, 这 里 有 一 个 关 于 提 交 信 息 的 快 速 说 明 有 一 个 好 的 创 建 提 交 的 准 则 并 且 坚 持 使 用 会 让 与 Git 工 作 和 与 其 他 人 协 作 更 容 易 Git 项 目 提 供 了 一 个 文 档, 其 中 列 举 了 关 于 创 建 提 交 到 提 交 补 丁 的 若 干 好 的 提 示 - 可 以 在 Git 源 代 码 中 的 Documentation/SubmittingPatches 文 件 中 阅 读 它 首 先, 你 不 会 想 要 把 空 白 错 误 ( 根 据 git help diff 的 描 述, 结 合 下 面 给 出 的 图 片, 空 白 错 误 是 指 行 尾 的 空 格 Tab 制 表 符, 和 行 首 空 格 后 跟 Tab 制 表 符 的 行 为 ) 提 交 上 去 Git 提 供 了 一 个 简 单 的 方 式 来 检 查 这 点 - 在 提 交 前, 运 行 git diff --check, 它 将 会 找 到 可 能 的 空 白 错 误 并 将 它 们 为 你 列 出 来

122 Figure 57. git diff --check 的 输 出 如 果 在 提 交 前 运 行 那 个 命 令, 可 以 知 道 提 交 中 是 否 包 含 可 能 会 使 其 他 开 发 者 恼 怒 的 空 白 问 题 接 下 来, 尝 试 让 每 一 个 提 交 成 为 一 个 逻 辑 上 的 独 立 变 更 集 如 果 可 以, 尝 试 让 改 动 可 以 理 解 - 不 要 在 整 个 周 末 编 码 解 决 五 个 问 题, 然 后 在 周 一 时 将 它 们 提 交 为 一 个 巨 大 的 提 交 即 使 在 周 末 期 间 你 无 法 提 交, 在 周 一 时 使 用 暂 存 区 域 将 你 的 工 作 最 少 拆 分 为 每 个 问 题 一 个 提 交, 并 且 为 每 一 个 提 交 附 带 一 个 有 用 的 信 息 如 果 其 中 一 些 改 动 修 改 了 同 一 个 文 件, 尝 试 使 用 git add --patch 来 部 分 暂 存 文 件 ( 在 交 互 式 暂 存 中 有 详 细 介 绍 ) 不 管 你 做 一 个 或 五 个 提 交, 只 要 所 有 的 改 动 是 在 同 一 时 刻 添 加 的, 项 目 分 支 末 端 的 快 照 就 是 独 立 的, 使 同 事 开 发 者 必 须 审 查 你 的 改 动 时 尽 量 让 事 情 容 易 些 当 你 之 后 需 要 时 这 个 方 法 也 会 使 拉 出 或 还 原 一 个 变 更 集 更 容 易 些 重 写 历 史 描 述 了 重 写 历 史 与 交 互 式 暂 存 文 件 的 若 干 有 用 的 Git 技 巧 - 在 将 工 作 发 送 给 其 他 人 前 使 用 这 些 工 具 来 帮 助 生 成 一 个 干 净 又 易 懂 的 历 史 最 后 一 件 要 牢 记 的 事 是 提 交 信 息 有 一 个 创 建 优 质 提 交 信 息 的 习 惯 会 使 Git 的 使 用 与 协 作 容 易 的 多 一 般 情 况 下, 信 息 应 当 以 少 于 50 个 字 符 (25 个 汉 字 ) 的 单 行 开 始 且 简 要 地 描 述 变 更, 接 着 是 一 个 空 白 行, 再 接 着 是 一 个 更 详 细 的 解 释 Git 项 目 要 求 一 个 更 详 细 的 解 释, 包 括 做 改 动 的 动 机 和 它 的 实 现 与 之 前 行 为 的 对 比 - 这 是 一 个 值 得 遵 循 的 好 规 则 在 这 些 信 息 中 使 用 现 在 时 态 祈 使 语 气 也 是 一 个 好 想 法 换 句 话 说, 使 用 命 令 使 用 Add tests for. 而 不 是 I added tests for 或 Adding tests for, 这 里 是 一 份 最 初 由 Tim Pope 写 的 模 板 :

123 修 改 的 摘 要 (50 个 字 符 或 更 少 ) 如 果 必 要 的 话, 加 入 更 详 细 的 解 释 文 字 在 大 概 72 个 字 符 的 时 候 换 行 在 某 些 情 形 下, 第 一 行 被 当 作 一 封 电 子 邮 件 的 标 题, 剩 下 的 文 本 作 为 正 文 分 隔 摘 要 与 正 文 的 空 行 是 必 须 的 ( 除 非 你 完 全 省 略 正 文 ); 如 果 你 将 两 者 混 在 一 起, 那 么 类 似 变 基 等 工 具 无 法 正 常 工 作 空 行 接 着 更 进 一 步 的 段 落 - 句 号 也 是 可 以 的 - 项 目 符 号 可 以 使 用 典 型 的 连 字 符 或 星 号 前 面 一 个 空 格, 之 间 用 空 行 隔 开, 但 是 可 以 依 据 不 同 的 惯 例 有 所 不 同 如 果 你 所 有 的 提 交 信 息 看 起 来 都 像 这 样, 对 你 与 跟 你 工 作 在 一 起 的 其 他 开 发 者 来 说 事 情 会 变 得 非 常 容 易 Git 项 目 有 一 个 良 好 格 式 化 的 提 交 信 息 - 尝 试 在 那 儿 运 行 git log --no-merges 来 看 看 漂 亮 的 格 式 化 的 项 目 提 交 历 史 像 什 么 样 在 接 下 来 的 例 子 中, 以 及 贯 穿 本 书 大 部 分, 出 于 简 洁 性 的 原 因 本 书 不 会 有 像 这 样 漂 亮 格 式 的 信 息 ; 相 反, 我 们 使 用 -m 选 项 的 git commit 照 我 们 说 的 做, 而 不 是 照 我 们 做 的 做 私 有 小 型 团 队 你 可 能 会 遇 到 的 最 简 单 的 配 置 是 有 一 两 个 其 他 开 发 者 的 私 有 项 目 私 有 在 这 个 上 下 文 中, 意 味 着 闭 源 - 不 可 以 从 外 面 的 世 界 中 访 问 到 你 和 其 他 的 开 发 者 都 有 仓 库 的 推 送 权 限 在 这 个 环 境 下, 可 以 采 用 一 个 类 似 使 用 Subversion 或 其 他 集 中 式 的 系 统 时 会 使 用 的 工 作 流 程 依 然 可 以 得 到 像 离 线 提 交 非 常 容 易 地 新 建 分 支 与 合 并 分 支 等 高 级 功 能, 但 是 工 作 流 程 可 以 是 很 简 单 的 ; 主 要 的 区 别 是 合 并 发 生 在 客 户 端 这 边 而 不 是 在 提 交 时 发 生 在 服 务 器 那 边 让 我 们 看 看 当 两 个 开 发 者 在 一 个 共 享 仓 库 中 一 起 工 作 时 会 是 什 么 样 子 第 一 个 开 发 者,John, 克 隆 了 仓 库, 做 了 改 动, 然 后 本 地 提 交 ( 为 了 缩 短 这 些 例 子 长 度, 协 议 信 息 已 被 替 换 为... ) # John's Machine $ git clone john@githost:simplegit.git Initialized empty Git repository in /home/john/simplegit/.git/... $ cd simplegit/ $ vim lib/simplegit.rb $ git commit -am 'removed invalid default value' [master 738ee87] removed invalid default value 1 files changed, 1 insertions(+), 1 deletions(-)

124 第 二 个 开 发 者,Jessica, 做 了 同 样 的 事 情 - 克 隆 仓 库 并 提 交 了 一 个 改 动 : # Jessica's Machine $ git clone jessica@githost:simplegit.git Initialized empty Git repository in /home/jessica/simplegit/.git/... $ cd simplegit/ $ vim TODO $ git commit -am 'add reset task' [master fbff5bc] add reset task 1 files changed, 1 insertions(+), 0 deletions(-) 现 在,Jessica 把 她 的 工 作 推 送 到 服 务 器 上 : # Jessica's Machine $ git push origin master... To jessica@githost:simplegit.git 1edee6b..fbff5bc master -> master John 也 尝 试 推 送 他 的 改 动 : # John's Machine $ git push origin master To john@githost:simplegit.git! [rejected] master -> master (non-fast forward) error: failed to push some refs to 'john@githost:simplegit.git' 不 允 许 John 推 送 是 因 为 在 同 一 时 间 Jessica 已 经 推 送 了 如 果 之 前 习 惯 于 用 Subversion 那 么 理 解 这 点 特 别 重 要, 因 为 你 会 注 意 到 两 个 开 发 者 并 没 有 编 辑 同 一 个 文 件 尽 管 Subversion 会 对 编 辑 的 不 同 文 件 在 服 务 器 上 自 动 进 行 一 次 合 并, 但 Git 要 求 你 在 本 地 合 并 提 交 John 必 须 抓 取 Jessica 的 改 动 并 合 并 它 们, 才 能 被 允 许 推 送 $ git fetch origin... From john@githost:simplegit + 049d078...fbff5bc master -> origin/master 在 这 个 时 候,John 的 本 地 仓 库 看 起 来 像 这 样 :

125 Figure 58. John 的 分 叉 历 史 John 有 一 个 引 用 指 向 Jessica 推 送 上 去 的 改 动, 但 是 他 必 须 将 它 们 合 并 入 自 己 的 工 作 中 之 后 才 能 被 允 许 推 送 $ git merge origin/master Merge made by recursive. TODO files changed, 1 insertions(+), 0 deletions(-) 合 并 进 行 地 很 顺 利 - John 的 提 交 历 史 现 在 看 起 来 像 这 样 :

126 Figure 59. 合 并 了 origin/master 之 后 John 的 仓 库 现 在,John 可 以 测 试 代 码, 确 保 它 依 然 正 常 工 作, 然 后 他 可 以 把 合 并 的 新 工 作 推 送 到 服 务 器 上 : $ git push origin master... To john@githost:simplegit.git fbff5bc..72bbc59 master -> master 最 终,John 的 提 交 历 史 看 起 来 像 这 样 : Figure 60. 推 送 到 origin 服 务 器 后 John 的 历 史 在 此 期 间,Jessica 在 一 个 特 性 分 支 上 工 作 她 创 建 了 一 个 称 作 issue54 的 特 性 分 支 并 且 在 那 个 分 支 上 做 了 三 次 提 交 她 还 没 有 抓 取 John 的 改 动, 所 以 她 的 提 交 历 史 看 起 来 像 这 样 :

127 Figure 61. Jessica 的 特 性 分 支 Jessica 想 要 与 John 同 步, 所 以 她 进 行 了 抓 取 操 作 : # Jessica's Machine $ git fetch origin... From jessica@githost:simplegit fbff5bc..72bbc59 master -> origin/master 那 会 同 时 拉 取 John 推 送 的 工 作 Jessica 的 历 史 现 在 看 起 来 像 这 样 : Figure 62. 抓 取 John 的 改 动 后 Jessica 的 历 史 Jessica 认 为 她 的 特 性 分 支 已 经 准 备 好 了, 但 是 她 想 要 知 道 必 须 合 并 什 么 进 入 她 的 工 作 才 能 推 送 她 运 行 git log 来 找 出 : $ git log --no-merges issue54..origin/master commit 738ee872852dfaa9d6634e0dea7a Author: John Smith <jsmith@example.com> Date: Fri May 29 16:01: removed invalid default value issue54..origin/master 语 法 是 一 个 日 志 过 滤 器, 要 求 Git 只 显 示 所 有 在 后 面 分 支 ( 在 本 例 中 是

128 origin/master) 但 不 在 前 面 分 支 ( 在 本 例 中 是 issue54) 的 提 交 的 列 表 我 们 将 会 在 提 交 区 间 中 详 细 介 绍 这 个 语 法 目 前, 我 们 可 以 从 输 出 中 看 到 有 一 个 John 生 成 的 但 是 Jessica 还 没 有 合 并 入 的 提 交 如 果 她 合 并 origin/master, 也 就 是 说 将 会 修 改 她 的 本 地 工 作 的 那 个 单 个 提 交 现 在,Jessica 可 以 合 并 她 的 特 性 工 作 到 她 的 master 分 支, 合 并 John 的 工 作 (origin/master) 进 入 她 的 master 分 支, 然 后 再 次 推 送 回 服 务 器 首 先, 为 了 整 合 所 有 这 些 工 作 她 切 换 回 她 的 master 分 支 $ git checkout master Switched to branch 'master' Your branch is behind 'origin/master' by 2 commits, and can be fastforwarded. 她 既 可 以 先 合 并 origin/master 也 可 以 先 合 并 issue54 - 它 们 都 是 上 游, 所 以 顺 序 并 没 有 关 系 不 论 她 选 择 的 顺 序 是 什 么 最 终 的 结 果 快 照 是 完 全 一 样 的 ; 只 是 历 史 会 有 一 点 轻 微 的 区 别 她 选 择 先 合 并 入 issue54: $ git merge issue54 Updating fbff5bc..4af4298 Fast forward README 1 + lib/simplegit.rb files changed, 6 insertions(+), 1 deletions(-) 没 有 发 生 问 题 ; 如 你 所 见 它 是 一 次 简 单 的 快 进 现 在 Jessica 合 并 入 John 的 工 作 (origin/master): $ git merge origin/master Auto-merging lib/simplegit.rb Merge made by recursive. lib/simplegit.rb files changed, 1 insertions(+), 1 deletions(-) 每 一 个 文 件 都 干 净 地 合 并 了,Jessica 的 历 史 看 起 来 像 这 样 :

129 Figure 63. 合 并 了 John 的 改 动 后 Jessica 的 历 史 现 在 origin/master 是 可 以 从 Jessica 的 master 分 支 到 达 的, 所 以 她 应 该 可 以 成 功 地 推 送 ( 假 设 同 一 时 间 John 并 没 有 再 次 推 送 ): $ git push origin master... To jessica@githost:simplegit.git 72bbc c15 master -> master 每 一 个 开 发 者 都 提 交 了 几 次 并 成 功 地 合 并 了 其 他 人 的 工 作 Figure 64. 推 送 所 有 的 改 动 回 服 务 器 后 Jessica 的 历 史 这 是 一 个 最 简 单 的 工 作 流 程 你 通 常 在 一 个 特 性 分 支 工 作 一 会 儿, 当 它 准 备 好 整 合 时 合 并 回 你 的 master 分 支 当 想 要 共 享 工 作 时, 将 其 合 并 回 你 自 己 的 master 分 支, 如 果 有 改 动 的 话 然 后 抓 取 并 合 并 origin/master, 最 终 推 送 到 服 务 器 上 的 master 分 支 通 常 顺 序 像 这 样 :

130 Figure 65. 一 个 简 单 的 多 人 Git 工 作 流 程 的 通 常 事 件 顺 序 私 有 管 理 团 队 在 接 下 来 的 情 形 中, 你 会 看 到 大 型 私 有 团 队 中 贡 献 者 的 角 色 在 你 将 学 习 到 的 这 种 工 作 环 境 中, 小 组 基 于 特 性 进 行 协 作, 这 些 团 队 的 贡 献 将 会 由 其 他 人 整 合

131 让 我 们 假 设 John 与 Jessica 在 一 个 特 性 上 工 作, 同 时 Jessica 与 Josie 在 第 二 个 特 性 上 工 作 在 本 例 中, 公 司 使 用 了 一 种 整 合 - 管 理 者 工 作 流 程, 独 立 小 组 的 工 作 只 能 被 特 定 的 工 程 师 整 合, 主 仓 库 的 master 分 支 只 能 被 那 些 工 程 师 更 新 在 这 种 情 况 下, 所 有 的 工 作 都 是 在 基 于 团 队 的 分 支 上 完 成 的 并 且 稍 后 会 被 整 合 者 拉 到 一 起 因 为 Jessica 在 两 个 特 性 上 工 作, 并 且 平 行 地 与 两 个 不 同 的 开 发 者 协 作, 让 我 们 跟 随 她 的 工 作 流 程 假 设 她 已 经 克 隆 了 仓 库, 首 先 决 定 在 featurea 上 工 作 她 为 那 个 特 性 创 建 了 一 个 新 分 支 然 后 在 那 做 了 一 些 工 作 : # Jessica's Machine $ git checkout -b featurea Switched to a new branch 'featurea' $ vim lib/simplegit.rb $ git commit -am 'add limit to log function' [featurea ] add limit to log function 1 files changed, 1 insertions(+), 1 deletions(-) 在 这 个 时 候, 她 需 要 将 工 作 共 享 给 John, 所 以 她 推 送 了 featurea 分 支 的 提 交 到 服 务 器 上 Jessica 没 有 master 分 支 的 推 送 权 限 - 只 有 整 合 者 有 - 所 以 为 了 与 John 协 作 必 须 推 送 另 一 个 分 支 $ git push -u origin featurea... To jessica@githost:simplegit.git * [new branch] featurea -> featurea Jessica 向 John 发 邮 件 告 诉 他 已 经 推 送 了 一 些 工 作 到 featurea 分 支 现 在 可 以 看 一 看 当 她 等 待 John 的 反 馈 时,Jessica 决 定 与 Josie 开 始 在 featureb 上 工 作 为 了 开 始 工 作, 她 基 于 服 务 器 的 master 分 支 开 始 了 一 个 新 分 支 # Jessica's Machine $ git fetch origin $ git checkout -b featureb origin/master Switched to a new branch 'featureb' 现 在,Jessica 在 featureb 分 支 上 创 建 了 几 次 提 交 :

132 $ vim lib/simplegit.rb $ git commit -am 'made the ls-tree function recursive' [featureb e5b0fdc] made the ls-tree function recursive 1 files changed, 1 insertions(+), 1 deletions(-) $ vim lib/simplegit.rb $ git commit -am 'add ls-files' [featureb ] add ls-files 1 files changed, 5 insertions(+), 0 deletions(-) Jessica 的 仓 库 看 起 来 像 这 样 : Figure 66. Jessica 的 初 始 提 交 历 史 她 准 备 好 推 送 工 作 了, 但 是 一 封 来 自 Josie 的 邮 件 告 知 一 些 初 始 工 作 已 经 被 推 送 到 服 务 器 上 的 featurebee 上 了 Jessica 在 能 推 送 到 服 务 器 前 首 先 需 要 将 那 些 改 动 与 她 自 己 的 合 并 然 后 她 可 以 通 过 git fetch 抓 取 Josie 的 改 动 : $ git fetch origin... From jessica@githost:simplegit * [new branch] featurebee -> origin/featurebee Jessica 现 在 可 以 通 过 git merge 将 其 合 并 到 她 做 的 工 作 中 :

133 $ git merge origin/featurebee Auto-merging lib/simplegit.rb Merge made by recursive. lib/simplegit.rb files changed, 4 insertions(+), 0 deletions(-) 有 点 儿 问 题 - 她 需 要 将 在 featureb 分 支 上 合 并 的 工 作 推 送 到 服 务 器 上 的 featurebee 分 支 她 可 以 通 过 指 定 本 地 分 支 加 上 冒 号 (:) 加 上 远 程 分 支 给 git push 命 令 来 这 样 做 : $ git push -u origin featureb:featurebee... To jessica@githost:simplegit.git fba9af8..cd685d1 featureb -> featurebee 这 称 作 一 个 引 用 规 格 查 看 引 用 规 格 了 解 关 于 Git 引 用 规 格 与 通 过 它 们 可 以 做 的 不 同 的 事 情 的 详 细 讨 论 也 要 注 意 -u 标 记 ; 这 是 --set-upstream 的 简 写, 该 标 记 会 为 之 后 轻 松 地 推 送 与 拉 取 配 置 分 支 紧 接 着,John 发 邮 件 给 Jessica 说 他 已 经 推 送 了 一 些 改 动 到 featurea 分 支 并 要 求 她 去 验 证 它 们 她 运 行 一 个 git fetch 来 拉 取 下 那 些 改 动 : $ git fetch origin... From jessica@githost:simplegit aad881d featurea -> origin/featurea 然 后, 通 过 git log 她 可 以 看 到 哪 些 发 生 了 改 变 : $ git log featurea..origin/featurea commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6 Author: John Smith <jsmith@example.com> Date: Fri May 29 19:57: changed log output to 30 from 25 最 终, 她 合 并 John 的 工 作 到 她 自 己 的 featurea 分 支 :

134 $ git checkout featurea Switched to branch 'featurea' $ git merge origin/featurea Updating aad881d Fast forward lib/simplegit.rb files changed, 9 insertions(+), 1 deletions(-) Jessica 想 要 轻 微 调 整 一 些 东 西, 所 以 她 再 次 提 交 然 后 将 其 推 送 回 服 务 器 : $ git commit -am 'small tweak' [featurea 774b3ed] small tweak 1 files changed, 1 insertions(+), 1 deletions(-) $ git push... To jessica@githost:simplegit.git b3ed featurea -> featurea Jessica 的 提 交 历 史 现 在 看 起 来 像 这 样 : Figure 67. 在 一 个 特 性 分 支 提 交 后 Jessica 的 历 史 Jessica Josie 与 John 通 知 整 合 者 在 服 务 器 上 的 featurea 与 featurebee 分 支 准 备 好 整 合 到 主 线 中 了 在 整 合 者 合 并 这 些 分 支 到 主 线 后, 一 次 抓 取 会 拿 下 来 一 个 新 的 合 并 提 交, 使 历 史 看 起 来 像 这 样 :

135 Figure 68. 合 并 了 Jessica 的 两 个 特 性 分 支 后 她 的 历 史 许 多 团 队 切 换 到 Git 是 因 为 这 一 允 许 多 个 团 队 并 行 工 作 并 在 之 后 合 并 不 同 工 作 的 能 力 团 队 中 更 小 一 些 的 子 小 组 可 以 通 过 远 程 分 支 协 作 而 不 必 影 响 或 妨 碍 整 个 团 队 的 能 力 是 Git 的 一 个 巨 大 优 势 在 这 儿 看 到 的 工 作 流 程 顺 序 类 似 这 样 :

136 Figure 69. 这 种 管 理 团 队 工 作 流 程 的 基 本 顺 序 派 生 的 公 开 项 目 向 公 开 项 目 做 贡 献 有 一 点 儿 不 同 因 为 没 有 权 限 直 接 更 新 项 目 的 分 支, 你 必 须 用 其 他 办 法 将 工 作 给 维 护 者 第 一 个 例 子 描 述 在 支 持 简 单 派 生 的 Git 托 管 上 使 用 派 生 来 做 贡 献 许 多 托 管 站 点 支 持 这 个 功 能 ( 包 括 GitHub BitBucket Google Code repo.or.cz 等 等 ), 许 多 项 目 维 护 者 期 望 这 种 风 格 的 贡 献 下 一 节 会 讨 论 偏 好 通 过 邮 件 接 受 贡 献 补 丁 的 项 目

137 首 先, 你 可 能 想 要 克 隆 主 仓 库, 为 计 划 贡 献 的 补 丁 或 补 丁 序 列 创 建 一 个 特 性 分 支, 然 后 在 那 儿 做 工 作 顺 序 看 起 来 基 本 像 这 样 : $ git clone (url) $ cd project $ git checkout -b featurea # (work) $ git commit # (work) $ git commit NOTE 你 可 能 会 想 要 使 用 rebase -i 来 将 工 作 压 缩 成 一 个 单 独 的 提 交, 或 者 重 排 提 交 中 的 工 作 使 补 丁 更 容 易 被 维 护 者 审 核 - 查 看 重 写 历 史 了 解 关 于 交 互 式 变 基 的 更 多 信 息 当 你 的 分 支 工 作 完 成 后 准 备 将 其 贡 献 回 维 护 者, 去 原 始 项 目 中 然 后 点 击 Fork 按 钮, 创 建 一 份 自 己 的 可 写 的 项 目 派 生 仓 库 然 后 需 要 添 加 这 个 新 仓 库 URL 为 第 二 个 远 程 仓 库, 在 本 例 中 称 作 myfork: $ git remote add myfork (url) 然 后 需 要 推 送 工 作 到 上 面 相 对 于 合 并 到 主 分 支 再 推 送 上 去, 推 送 你 正 在 工 作 的 特 性 分 支 到 仓 库 上 更 简 单 原 因 是 工 作 如 果 不 被 接 受 或 者 是 被 拣 选 的, 就 不 必 回 退 你 的 master 分 支 如 果 维 护 者 合 并 变 基 或 拣 选 你 的 工 作, 不 管 怎 样 你 最 终 会 通 过 拉 取 他 们 的 仓 库 找 回 来 你 的 工 作 $ git push -u myfork featurea 当 工 作 已 经 被 推 送 到 你 的 派 生 后, 你 需 要 通 知 维 护 者 这 通 常 被 称 作 一 个 拉 取 请 求 (pull request), 你 既 可 以 通 过 网 站 生 成 它 - GitHub 有 它 自 己 的 Pull Request 机 制, 我 们 将 会 在 GitHub 介 绍 - 也 可 以 运 行 git request-pull 命 令 然 后 手 动 地 将 输 出 发 送 电 子 邮 件 给 项 目 的 维 护 者 request-pull 命 令 接 受 特 性 分 支 拉 入 的 基 础 分 支, 以 及 它 们 拉 入 的 Git 仓 库 URL, 输 出 请 求 拉 入 的 所 有 修 改 的 总 结 例 如,Jessica 想 要 发 送 给 John 一 个 拉 取 请 求, 她 已 经 在 刚 刚 推 送 的 分 支 上 做 了 两 次 提 交 她 可 以 运 行 这 个 :

138 $ git request-pull origin/master myfork The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40: John Smith (1): added a new function are available in the git repository at: git://githost/simplegit.git featurea Jessica Smith (2): add limit to log function change log output to 30 from 25 lib/simplegit.rb files changed, 9 insertions(+), 1 deletions(-) 这 个 输 出 可 以 被 发 送 给 维 护 者 - 它 告 诉 他 们 工 作 是 从 哪 个 分 支 开 始 归 纳 的 提 交 与 从 哪 里 拉 入 这 些 工 作 在 一 个 你 不 是 维 护 者 的 项 目 上, 通 常 有 一 个 总 是 跟 踪 origin/master 的 master 分 支 会 很 方 便, 在 特 性 分 支 上 做 工 作 是 因 为 如 果 它 们 被 拒 绝 时 你 可 以 轻 松 地 丢 弃 如 果 同 一 时 间 主 仓 库 移 动 了 然 后 你 的 提 交 不 再 能 干 净 地 应 用, 那 么 使 工 作 主 题 独 立 于 特 性 分 支 也 会 使 你 变 基 (rebase) 工 作 时 更 容 易 例 如, 你 想 要 提 供 第 二 个 特 性 工 作 到 项 目, 不 要 继 续 在 刚 刚 推 送 的 特 性 分 支 上 工 作 - 从 主 仓 库 的 master 分 支 重 新 开 始 : $ git checkout -b featureb origin/master # (work) $ git commit $ git push myfork featureb # ( maintainer) $ git fetch origin 现 在, 每 一 个 特 性 都 保 存 在 一 个 贮 藏 库 中 - 类 似 于 补 丁 队 列 - 可 以 重 写 变 基 与 修 改 而 不 会 让 特 性 互 相 干 涉 或 互 相 依 赖, 像 这 样 :

139 Figure 70. featureb 的 初 始 提 交 历 史 假 设 项 目 维 护 者 已 经 拉 取 了 一 串 其 他 补 丁, 然 后 尝 试 拉 取 你 的 第 一 个 分 支, 但 是 没 有 干 净 地 合 并 在 这 种 情 况 下, 可 以 尝 试 变 基 那 个 分 支 到 origin/master 的 顶 部, 为 维 护 者 解 决 冲 突, 然 后 重 新 提 交 你 的 改 动 : $ git checkout featurea $ git rebase origin/master $ git push -f myfork featurea 这 样 会 重 写 你 的 历 史, 现 在 看 起 来 像 是 featurea 工 作 之 后 的 提 交 历 史 Figure 71. featurea 工 作 之 后 的 提 交 历 史 因 为 你 将 分 支 变 基 了, 所 以 必 须 为 推 送 命 令 指 定 -f 选 项, 这 样 才 能 将 服 务 器 上 有 一 个 不 是 它 的 后 代 的 提 交 的 featurea 分 支 替 换 掉 一 个 替 代 的 选 项 是 推 送 这 个 新 工 作 到 服 务 器 上 的 一 个 不 同 分 支 ( 可 能 称 作 featureav2) 让 我 们 看 一 个 更 有 可 能 的 情 况 : 维 护 者 看 到 了 你 的 第 二 个 分 支 上 的 工 作 并 且 很 喜 欢 其 中 的 概 念, 但 是 想 要 你 修 改 一 下 实 现 的 细 节 你 也 可 以 利 用 这 次 机 会 将 工 作 基 于 项 目 现 在 的 master 分 支 你 从 现 在 的 origin/master 分 支 开 始 一 个 新 分 支, 在 那 儿 压 缩 featureb 的 改 动, 解 决 任 何 冲 突, 改 变 实 现, 然 后 推 送 它 为 一 个 新 分 支

140 $ git checkout -b featurebv2 origin/master $ git merge --no-commit --squash featureb # (change implementation) $ git commit $ git push myfork featurebv2 --squash 选 项 接 受 被 合 并 的 分 支 上 的 所 有 工 作, 并 将 其 压 缩 至 一 个 变 更 集, 使 仓 库 变 成 一 个 真 正 的 合 并 发 生 的 状 态, 而 不 会 真 的 生 成 一 个 合 并 提 交 这 意 味 着 你 的 未 来 的 提 交 将 会 只 有 一 个 父 提 交, 并 允 许 你 引 入 另 一 个 分 支 的 所 有 改 动, 然 后 在 记 录 一 个 新 提 交 前 做 更 多 的 改 动 同 样 --no-commit 选 项 在 默 认 合 并 过 程 中 可 以 用 来 延 迟 生 成 合 并 提 交 现 在 你 可 以 给 维 护 者 发 送 一 条 消 息, 表 示 你 已 经 做 了 要 求 的 修 改 然 后 他 们 可 以 在 你 的 featurebv2 分 支 上 找 到 那 些 改 动 Figure 72. featurebv2 工 作 之 后 的 提 交 历 史 通 过 邮 件 的 公 开 项 目 许 多 项 目 建 立 了 接 受 补 丁 的 流 程 - 需 要 检 查 每 一 个 项 目 的 特 定 规 则, 因 为 它 们 之 间 有 区 别 因 为 有 几 个 历 史 悠 久 的 大 型 的 项 目 会 通 过 一 个 开 发 者 的 邮 件 列 表 接 受 补 丁, 现 在 我 们 将 会 通 过 一 个 例 子 来 演 示 工 作 流 程 与 之 前 的 用 例 是 类 似 的 - 你 为 工 作 的 每 一 个 补 丁 序 列 创 建 特 性 分 支 区 别 是 如 何 提 交 它 们 到 项 目 中 生 成 每 一 个 提 交 序 列 的 电 子 邮 件 版 本 然 后 邮 寄 它 们 到 开 发 者 邮 件 列 表, 而 不 是 派 生 项 目 然 后 推 送 到 你 自 己 的 可 写 版 本 $ git checkout -b topica # (work) $ git commit # (work) $ git commit 现 在 有 两 个 提 交 要 发 送 到 邮 件 列 表 使 用 git format-patch 来 生 成 可 以 邮 寄 到 列 表 的 mbox 格 式 的 文 件 - 它 将 每 一 个 提 交 转 换 为 一 封 电 子 邮 件, 提 交 信 息 的 第 一 行 作 为 主 题, 剩 余 信 息 与 提 交 引 入 的 补 丁 作 为 正 文 它 有 一

141 个 好 处 是 是 使 用 format-patch 生 成 的 一 封 电 子 邮 件 应 用 的 提 交 正 确 地 保 留 了 所 有 的 提 交 信 息 $ git format-patch -M origin/master 0001-add-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch format-patch 命 令 打 印 出 它 创 建 的 补 丁 文 件 名 字 -M 开 关 告 诉 Git 查 找 重 命 名 文 件 最 后 看 起 来 像 这 样 : $ cat 0001-add-limit-to-log-function.patch From d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00: From: Jessica Smith <jessica@example.com> Date: Sun, 6 Apr :17: Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first lib/simplegit.rb files changed, 1 insertions(+), 1 deletions(-) diff --git a/lib/simplegit.rb b/lib/simplegit.rb index 76f47bc..f9815f a/lib/simplegit.rb ,7 class SimpleGit end def log(treeish = 'master') - command("git log #{treeish}") + command("git log -n 20 #{treeish}") end def ls_tree(treeish = 'master') 也 可 以 编 辑 这 些 补 丁 文 件 为 邮 件 列 表 添 加 更 多 不 想 要 在 提 交 信 息 中 显 示 出 来 的 信 息 如 果 在 --- 行 与 补 丁 开 头 (diff --git 行 ) 之 间 添 加 文 本, 那 么 开 发 者 就 可 以 阅 读 它 ; 但 是 应 用 补 丁 时 会 排 除 它 为 了 将 其 邮 寄 到 邮 件 列 表, 你 既 可 以 将 文 件 粘 贴 进 电 子 邮 件 客 户 端, 也 可 以 通 过 命 令 行 程 序 发 送 它 粘 贴 文 本 经 常 会 发 生 格 式 化 问 题, 特 别 是 那 些 不 会 合 适 地 保 留 换 行 符 与 其 他 空 白 的 更 聪 明 的 客 户 端 幸 运 的 是,Git 提 供 了 一 个 工 具 帮 助 你 通 过 IMAP 发 送 正 确 格 式 化 的 补 丁, 这 可 能 对 你 更 容 易 些 我 们 将 会 演 示 如 何 通 过 Gmail 发 送 一 个 补 丁, 它 正 好 是 我 们 所 知 最 好 的 邮 件 代 理 ; 可 以 在 之 前 提 到 的 Git 源 代 码 中 的 Documentation/SubmittingPatches 文 件 的 最 下 面 了 解 一 系 列 邮 件 程 序 的 详 细 指 令

142 首 先, 需 要 在 ~/.gitconfig 文 件 中 设 置 imap 区 块 可 以 通 过 一 系 列 的 git config 命 令 来 分 别 设 置 每 一 个 值, 或 者 手 动 添 加 它 们, 不 管 怎 样 最 后 配 置 文 件 应 该 看 起 来 像 这 样 : [imap] folder = "[Gmail]/Drafts" host = imaps://imap.gmail.com user = user@gmail.com pass = p4ssw0rd port = 993 sslverify = false 如 果 IMAP 服 务 器 不 使 用 SSL, 最 后 两 行 可 能 没 有 必 要,host 的 值 会 是 imap:// 而 不 是 imaps:// 当 那 些 设 置 完 成 后, 可 以 使 用 git imap-send 将 补 丁 序 列 放 在 特 定 IMAP 服 务 器 的 Drafts 文 件 夹 中 : $ cat *.patch git imap-send Resolving imap.gmail.com... ok Connecting to [ ]: ok Logging in... sending 2 messages 100% (2/2) done 在 这 个 时 候, 你 应 该 能 够 到 Drafts 文 件 夹 中, 修 改 收 件 人 字 段 为 想 要 发 送 补 丁 的 邮 件 列 表, 可 能 需 要 抄 送 给 维 护 者 或 负 责 那 个 部 分 的 人, 然 后 发 送 你 也 可 以 通 过 一 个 SMTP 服 务 器 发 送 补 丁 同 之 前 一 样, 你 可 以 通 过 一 系 列 的 git config 命 令 来 分 别 设 置 选 项, 或 者 你 可 以 手 动 地 将 它 们 添 加 到 你 的 ~/.gitconfig 文 件 的 sendmail 区 块 : [send ] smtpencryption = tls smtpserver = smtp.gmail.com smtpuser = user@gmail.com smtpserverport = 587 当 这 完 成 后, 你 可 以 使 用 git send- 发 送 你 的 补 丁 :

143 $ git send- *.patch 0001-added-limit-to-log-function.patch 0002-changed-log-output-to-30-from-25.patch Who should the s appear to be from? [Jessica Smith s will be sent from: Jessica Smith Who should the s be sent to? Message-ID to be used as In-Reply-To for the first ? y 然 后, 对 于 正 在 发 送 的 每 一 个 补 丁,Git 会 吐 出 这 样 的 一 串 日 志 信 息 : (mbox) Adding cc: Jessica Smith <jessica@example.com> from \line 'From: Jessica Smith <jessica@example.com>' OK. Log says: Sendmail: /usr/sbin/sendmail -i jessica@example.com From: Jessica Smith <jessica@example.com> To: jessica@example.com Subject: [PATCH 1/2] added limit to log function Date: Sat, 30 May :29: Message-Id: < git-send- -jessica@example.com> X-Mailer: git-send rc1.20.g8c5b.dirty In-Reply-To: <y> References: <y> Result: OK 总 结 这 个 部 分 介 绍 了 处 理 可 能 会 遇 到 的 几 个 迥 然 不 同 类 型 的 Git 项 目 的 一 些 常 见 的 工 作 流 程, 介 绍 了 帮 助 管 理 这 个 过 程 的 一 些 新 工 具 接 下 来, 你 会 了 解 到 如 何 在 贡 献 的 另 一 面 工 作 : 维 护 一 个 Git 项 目 你 将 会 学 习 如 何 成 为 一 个 仁 慈 的 独 裁 者 或 整 合 管 理 者 维 护 项 目 除 了 如 何 有 效 地 参 与 一 个 项 目 的 贡 献 之 外, 你 可 能 也 需 要 了 解 如 何 维 护 项 目 这 包 含 接 受 并 应 用 别 人 使 用 format-patch 生 成 并 通 过 电 子 邮 件 发 送 过 来 的 补 丁, 或 对 项 目 添 加 的 远 程 版 本 库 分 支 中 的 更 改 进 行 整 合 但 无 论 是 管 理 版 本 库, 还 是 帮 忙 验 证 审 核 收 到 的 补 丁, 都 需 要 同 其 他 贡 献 者 约 定 某 种 长 期 可 持 续 的 工 作 方 式 在 特 性 分 支 中 工 作 如 果 你 想 向 项 目 中 整 合 一 些 新 东 西, 最 好 将 这 些 尝 试 局 限 在 特 性 分 支 一 种 通 常 用 来 尝 试 新 东 西 的 临 时 分 支 中 这 样 便 于 单 独 调 整 补 丁, 如 果 遇 到 无 法 正 常 工 作 的 情 况, 可 以 先 不 用 管, 等 到 有 时 间 的 时 候 再 来 处 理 如 果 你 基 于 你 所 尝 试 进 行 工 作 的 特 性 为 分 支 创 建 一 个 简 单 的 名 字, 比 如 ruby_client 或 者 具 有 类 似 描 述 性 的 其 他 名 字, 这 样 即 使 你 必 须 暂 时 抛 弃 它, 以 后 回 来 时 也 不 会 忘 记 项 目 的 维 护 者 一 般 还 会 为 这 些 分 支 附 带 命 名 空 间, 比

144 如 sc/ruby_client( 其 中 sc 是 贡 献 该 项 工 作 的 人 名 称 的 简 写 ) 你 应 该 记 得, 可 以 使 用 如 下 方 式 基 于 master 分 支 建 立 特 性 分 支 : $ git branch sc/ruby_client master 或 者 如 果 你 同 时 想 立 刻 切 换 到 新 分 支 上 的 话, 可 以 使 用 checkout -b 选 项 : $ git checkout -b sc/ruby_client master 现 在 你 已 经 准 备 好 将 别 人 贡 献 的 工 作 加 入 到 这 个 特 性 分 支, 并 考 虑 是 否 将 其 合 并 到 长 期 分 支 中 去 了 应 用 来 自 邮 件 的 补 丁 如 果 你 通 过 电 子 邮 件 收 到 了 一 个 需 要 整 合 进 入 项 目 的 补 丁, 你 需 要 将 其 应 用 到 特 性 分 支 中 进 行 评 估 有 两 种 应 用 该 种 补 丁 的 方 法 : 使 用 git apply, 或 者 使 用 git am 使 用 apply 命 令 应 用 补 丁 如 果 你 收 到 了 一 个 使 用 git diff 或 Unix diff 命 令 ( 不 推 荐 使 用 这 种 方 式, 具 体 见 下 一 节 ) 创 建 的 补 丁, 可 以 使 用 git apply 命 令 来 应 用 假 设 你 将 补 丁 保 存 在 了 /tmp/patch-ruby-client.patch 中, 可 以 这 样 应 用 补 丁 : $ git apply /tmp/patch-ruby-client.patch 这 会 修 改 工 作 目 录 中 的 文 件 它 与 运 行 patch -p1 命 令 来 应 用 补 丁 几 乎 是 等 效 的, 但 是 这 种 方 式 更 加 严 格, 相 对 于 patch 来 说, 它 能 够 接 受 的 模 糊 匹 配 更 少 它 也 能 够 处 理 git diff 格 式 文 件 所 描 述 的 文 件 添 加 删 除 和 重 命 名 操 作, 而 patch 则 不 会 最 后,git apply 命 令 采 用 了 一 种 全 部 应 用, 否 则 就 全 部 撤 销 (apply all or abort all) 的 模 型, 即 补 丁 只 有 全 部 内 容 都 被 应 用 和 完 全 不 被 应 用 两 个 状 态, 而 patch 可 能 会 导 致 补 丁 文 件 被 部 分 应 用, 最 后 使 你 的 工 作 目 录 保 持 在 一 个 比 较 奇 怪 的 状 态 总 体 来 看,git apply 命 令 要 比 patch 谨 慎 得 多 并 且, 它 不 会 为 你 创 建 提 交 在 运 行 之 后, 你 需 要 手 动 暂 存 并 提 交 补 丁 所 引 入 的 更 改 在 实 际 应 用 补 丁 前, 你 还 可 以 使 用 git apply 来 检 查 补 丁 是 否 可 以 顺 利 应 用 即 对 补 丁 运 行 git apply --check 命 令 : $ git apply --check 0001-seeing-if-this-helps-the-gem.patch error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply 如 果 没 有 产 生 输 出, 则 该 补 丁 可 以 顺 利 应 用 如 果 检 查 失 败 了, 该 命 令 还 会 以 一 个 非 零 的 状 态 退 出, 所 以 需 要 时 你 也 可 以 在 脚 本 中 使 用 它

145 使 用 am 命 令 应 用 补 丁 如 果 补 丁 的 贡 献 者 也 是 一 个 Git 用 户, 并 且 其 能 熟 练 使 用 format-patch 命 令 来 生 成 补 丁, 这 样 的 话 你 的 工 作 会 变 得 更 加 轻 松, 因 为 这 种 补 丁 中 包 含 了 作 者 信 息 和 提 交 信 息 供 你 参 考 如 果 可 能 的 话, 请 鼓 励 贡 献 者 使 用 format-patch 而 不 是 diff 来 为 你 生 成 补 丁 而 只 有 对 老 式 的 补 丁, 你 才 必 须 使 用 git apply 命 令 要 应 用 一 个 由 format-patch 命 令 生 成 的 补 丁, 你 应 该 使 用 git am 命 令 从 技 术 的 角 度 看,git am 是 为 了 读 取 mbox 文 件 而 构 建 的,mbox 是 一 种 用 来 在 单 个 文 本 文 件 中 存 储 一 个 或 多 个 电 子 邮 件 消 息 的 简 单 纯 文 本 格 式 其 大 致 格 式 如 下 所 示 : From d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00: From: Jessica Smith <jessica@example.com> Date: Sun, 6 Apr :17: Subject: [PATCH 1/2] add limit to log function Limit log functionality to the first 20 这 其 实 就 是 你 前 面 看 到 的 format-patch 命 令 输 出 的 开 始 几 行 而 同 时 它 也 是 有 效 的 mbox 电 子 邮 件 格 式 如 果 有 人 使 用 git send- 命 令 将 补 丁 以 电 子 邮 件 的 形 式 发 送 给 你, 你 便 可 以 将 它 下 载 为 mbox 格 式 的 文 件, 之 后 将 git am 命 令 指 向 该 文 件, 它 会 应 用 其 中 包 含 的 所 有 补 丁 如 果 你 所 使 用 的 邮 件 客 户 端 能 够 同 时 将 多 封 邮 件 保 存 为 mbox 格 式 的 文 件, 你 甚 至 能 够 将 一 系 列 补 丁 打 包 为 单 个 mbox 文 件, 并 利 用 git am 命 令 将 它 们 一 次 性 全 部 应 用 然 而, 如 果 贡 献 者 将 format-patch 生 成 的 补 丁 文 件 上 传 到 类 似 Request Ticket 的 任 务 处 理 系 统, 你 可 以 先 将 其 保 存 到 本 地, 之 后 通 过 git am 来 应 用 补 丁 : $ git am 0001-limit-log-function.patch Applying: add limit to log function 你 会 看 到 补 丁 被 顺 利 地 应 用, 并 且 为 你 自 动 创 建 了 一 个 新 的 提 交 其 中 的 作 者 信 息 来 自 于 电 子 邮 件 头 部 的 From 和 Date 字 段, 提 交 消 息 则 取 自 Subject 和 邮 件 正 文 中 补 丁 之 前 的 内 容 比 如, 应 用 上 面 那 个 mbox 示 例 后 生 成 的 提 交 是 这 样 的 : $ git log --pretty=fuller -1 commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0 Author: Jessica Smith <jessica@example.com> AuthorDate: Sun Apr 6 10:17: Commit: Scott Chacon <schacon@gmail.com> CommitDate: Thu Apr 9 09:19: add limit to log function Limit log functionality to the first 20

146 其 中 Commit 信 息 表 示 的 是 应 用 补 丁 的 人 和 应 用 补 丁 的 时 间 Author 信 息 则 表 示 补 丁 的 原 作 者 和 原 本 的 创 建 时 间 但 是, 有 时 候 无 法 顺 利 地 应 用 补 丁 这 也 许 是 因 为 你 的 主 分 支 和 创 建 补 丁 的 分 支 相 差 较 多, 也 有 可 能 是 因 为 这 个 补 丁 依 赖 于 其 他 你 尚 未 应 用 的 补 丁 这 种 情 况 下,git am 进 程 将 会 报 错 并 且 询 问 你 要 做 什 么 : $ git am 0001-seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gem error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply Patch failed at When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". 该 命 令 将 会 在 所 有 出 现 问 题 的 文 件 内 加 入 冲 突 标 记, 就 和 发 生 冲 突 的 合 并 或 变 基 操 作 一 样 而 你 解 决 问 题 的 手 段 很 大 程 度 上 也 是 一 样 的 即 手 动 编 辑 那 些 文 件 来 解 决 冲 突, 暂 存 新 的 文 件, 之 后 运 行 git am --resolved 继 续 应 用 下 一 个 补 丁 : $ (fix the file) $ git add ticgit.gemspec $ git am --resolved Applying: seeing if this helps the gem 如 果 你 希 望 Git 能 够 尝 试 以 更 加 智 能 的 方 式 解 决 冲 突, 你 可 以 对 其 传 递 -3 选 项 来 使 Git 尝 试 进 行 三 方 合 并 该 选 项 默 认 并 没 有 打 开, 因 为 如 果 用 于 创 建 补 丁 的 提 交 并 不 在 你 的 版 本 库 内 的 话, 这 样 做 是 没 有 用 处 的 而 如 果 你 确 实 有 那 个 提 交 的 话 比 如 补 丁 是 基 于 某 个 公 共 提 交 的 那 么 通 常 -3 选 项 对 于 应 用 有 冲 突 的 补 丁 是 更 加 明 智 的 选 择 $ git am seeing-if-this-helps-the-gem.patch Applying: seeing if this helps the gem error: patch failed: ticgit.gemspec:1 error: ticgit.gemspec: patch does not apply Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... No changes -- Patch already applied. 比 如 上 面 这 种 情 况, 我 在 之 前 已 经 应 用 过 同 样 的 补 丁 如 果 没 有 -3 选 项 的 话, 这 看 起 来 就 像 是 存 在 一 个 冲 突 如 果 你 正 在 利 用 一 个 mbox 文 件 应 用 多 个 补 丁, 也 可 以 在 交 互 模 式 下 运 行 am 命 令, 这 样 在 每 个 补 丁 之 前, 它 会 停 住 询 问 你 是 否 要 应 用 该 补 丁 :

147 $ git am -3 -i mbox Commit Body is: seeing if this helps the gem Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all 这 在 你 保 存 的 补 丁 较 多 时 很 好 用, 因 为 你 可 以 在 应 用 之 前 查 看 忘 掉 内 容 的 补 丁, 并 且 跳 过 已 经 应 用 过 的 补 丁 当 与 你 的 特 性 相 关 的 所 有 补 丁 都 被 应 用 并 提 交 到 分 支 中 之 后, 你 就 可 以 选 择 是 否 以 及 如 何 将 其 整 合 到 更 长 期 的 分 支 中 去 了 检 出 远 程 分 支 如 果 你 的 贡 献 者 建 立 了 自 己 的 版 本 库, 并 且 向 其 中 推 送 了 若 干 修 改, 之 后 将 版 本 库 的 URL 和 包 含 更 改 的 远 程 分 支 发 送 给 你, 那 么 你 可 以 将 其 添 加 为 一 个 远 程 分 支, 并 且 在 本 地 进 行 合 并 比 如 Jessica 向 你 发 送 了 一 封 电 子 邮 件, 内 容 是 在 她 的 版 本 库 中 的 ruby-client 分 支 中 有 一 个 很 不 错 的 新 功 能, 为 了 测 试 该 功 能, 你 可 以 将 其 添 加 为 一 个 远 程 分 支, 并 在 本 地 检 出 : $ git remote add jessica git://github.com/jessica/myproject.git $ git fetch jessica $ git checkout -b rubyclient jessica/ruby-client 如 果 她 再 次 发 邮 件 说 另 一 个 分 支 中 包 含 另 一 个 优 秀 功 能, 因 为 之 前 已 经 设 置 好 远 程 分 支 了, 你 就 可 以 直 接 进 行 抓 取 及 检 出 操 作 这 对 于 与 他 人 长 期 合 作 工 作 来 说 很 有 用 而 对 于 提 交 补 丁 频 率 较 小 的 贡 献 者, 相 对 于 每 个 人 维 护 自 己 的 服 务 器, 不 断 增 删 远 程 分 支 的 做 法, 使 用 电 子 邮 件 来 接 收 可 能 会 比 较 省 时 况 且 你 也 不 会 想 要 加 入 数 百 个 只 提 供 一 两 个 补 丁 的 远 程 分 支 然 而, 脚 本 和 托 管 服 务 在 一 定 程 度 上 可 以 简 化 这 些 工 作 这 很 大 程 度 上 依 赖 于 你 和 你 的 贡 献 者 开 发 的 方 式 这 种 方 式 的 另 一 种 优 点 是 你 可 以 同 时 得 到 提 交 历 史 虽 然 代 码 合 并 中 可 能 会 出 现 问 题, 但 是 你 能 获 知 他 人 的 工 作 是 基 于 你 的 历 史 中 的 具 体 哪 一 个 位 置 ; 所 以 Git 会 默 认 进 行 三 方 合 并, 不 需 要 提 供 -3 选 项, 你 也 不 需 要 担 心 补 丁 是 基 于 某 个 你 无 法 访 问 的 提 交 生 成 的 对 于 非 持 续 性 的 合 作, 如 果 你 依 然 想 要 以 这 种 方 式 拉 取 数 据 的 话, 你 可 以 对 远 程 版 本 库 的 URL 调 用 git pull 命 令 这 会 执 行 一 个 一 次 性 的 抓 取, 而 不 会 将 该 URL 存 为 远 程 引 用 : $ git pull From * branch HEAD -> FETCH_HEAD Merge made by recursive.

148 确 定 引 入 了 哪 些 东 西 你 已 经 有 了 一 个 包 含 其 他 人 贡 献 的 特 性 分 支 现 在 你 可 以 决 定 如 何 处 理 它 们 了 本 节 回 顾 了 若 干 命 令, 以 便 于 你 检 查 若 将 其 合 并 入 主 分 支 所 引 入 的 更 改 一 般 来 说, 你 应 该 对 该 分 支 中 所 有 master 分 支 尚 未 包 含 的 提 交 进 行 检 查 通 过 在 分 支 名 称 前 加 入 --not 选 项, 你 可 以 排 除 master 分 支 中 的 提 交 这 和 我 们 之 前 使 用 的 master..contrib 格 式 是 一 样 的 假 设 贡 献 者 向 你 发 送 了 两 个 补 丁, 为 此 你 创 建 了 一 个 名 叫 contrib 的 分 支 并 在 其 上 应 用 补 丁, 你 可 以 运 行 : $ git log contrib --not master commit 5b6235bd efc4d73316f0a68d484f118 Author: Scott Chacon <schacon@gmail.com> Date: Fri Oct 24 09:53: seeing if this helps the gem commit 7482e0d16d04bea79d0dba8988cc78df655f16a0 Author: Scott Chacon <schacon@gmail.com> Date: Mon Oct 22 19:38: updated the gemspec to hopefully work better 如 果 要 查 看 每 次 提 交 所 引 入 的 具 体 修 改, 你 应 该 记 得 可 以 给 git log 命 令 传 递 -p 选 项, 这 样 它 会 在 每 次 提 交 后 面 附 加 对 应 的 差 异 (diff) 而 要 查 看 将 该 特 性 分 支 与 另 一 个 分 支 合 并 的 完 整 diff, 你 可 能 需 要 使 用 一 个 有 些 奇 怪 的 技 巧 来 得 到 正 确 的 结 果 你 可 能 会 想 到 这 种 方 式 : $ git diff master 这 个 命 令 会 输 出 一 个 diff, 但 它 可 能 并 不 是 我 们 想 要 的 如 果 在 你 创 建 特 性 分 支 之 后,master 分 支 向 前 移 动 了, 你 获 得 的 结 果 就 会 显 得 有 些 不 对 这 是 因 为 Git 会 直 接 将 该 特 性 分 支 与 master 分 支 的 最 新 提 交 快 照 进 行 比 较 比 如 说 你 在 master 分 支 中 向 某 个 文 件 添 加 了 一 行 内 容, 那 么 直 接 比 对 最 新 快 照 的 结 果 看 上 去 就 像 是 你 在 特 性 分 支 中 将 这 一 行 删 除 了 如 果 master 分 支 是 你 的 特 性 分 支 的 直 接 祖 先, 其 实 是 没 有 任 何 问 题 的 ; 但 是 一 旦 两 个 分 支 的 历 史 产 生 了 分 叉, 上 述 比 对 产 生 的 diff 看 上 去 就 像 是 将 特 性 分 支 中 所 有 的 新 东 西 加 入, 并 且 将 master 分 支 所 独 有 的 东 西 删 除 而 你 真 正 想 要 检 查 的 东 西, 实 际 上 仅 仅 是 特 性 分 支 所 添 加 的 更 改 也 就 是 该 分 支 与 master 分 支 合 并 所 要 引 入 的 工 作 要 达 到 此 目 的, 你 需 要 让 Git 对 特 性 分 支 上 最 新 的 提 交 与 该 分 支 与 master 分 支 的 首 个 公 共 祖 先 进 行 比 较 从 技 术 的 角 度 讲, 你 可 以 以 手 工 的 方 式 找 出 公 共 祖 先, 并 对 其 显 式 运 行 diff 命 令 :

149 $ git merge-base contrib master 36c7dba2c95e6bbb78dfa822519ecfec6e1ca649 $ git diff 36c7db 然 而, 这 种 做 法 比 较 麻 烦, 所 以 Git 提 供 了 一 种 比 较 便 捷 的 方 式 : 三 点 语 法 对 于 diff 命 令 来 说, 你 可 以 通 过 把... 置 于 另 一 个 分 支 名 后 来 对 该 分 支 的 最 新 提 交 与 两 个 分 支 的 共 同 祖 先 进 行 比 较 : $ git diff master...contrib 该 命 令 仅 会 显 示 自 当 前 特 性 分 支 与 master 分 支 的 共 同 祖 先 起, 该 分 支 中 的 工 作 这 个 语 法 很 有 用, 应 该 牢 记 将 贡 献 的 工 作 整 合 进 来 当 特 性 分 支 中 所 有 的 工 作 都 已 经 准 备 好 整 合 进 入 更 靠 近 主 线 的 分 支 时, 接 下 来 的 问 题 就 是 如 何 进 行 整 合 了 此 外, 还 有 一 个 问 题 是, 你 想 使 用 怎 样 的 总 体 工 作 流 来 维 护 你 的 项 目? 你 的 选 择 有 很 多, 我 们 会 介 绍 其 中 的 一 部 分 合 并 工 作 流 一 种 非 常 简 单 的 工 作 流 会 直 接 将 工 作 合 并 进 入 master 分 支 在 这 种 情 况 下,master 分 支 包 含 的 代 码 是 基 本 稳 定 的 当 你 完 成 某 个 特 性 分 支 的 工 作, 或 审 核 通 过 了 其 他 人 所 贡 献 的 工 作 时, 你 会 将 其 合 并 进 入 master 分 支, 之 后 将 特 性 分 支 删 除, 如 此 反 复 如 果 我 们 的 版 本 库 包 含 类 似 包 含 若 干 特 性 分 支 的 提 交 历 史 的 两 个 名 称 分 别 为 ruby_client 和 php_client 的 分 支, 并 且 我 们 先 合 并 ruby_client 分 支, 之 后 合 并 php_client 分 支, 那 么 提 交 历 史 最 后 会 变 成 合 并 特 性 分 支 之 后 的 样 子 Figure 73. 包 含 若 干 特 性 分 支 的 提 交 历 史

150 Figure 74. 合 并 特 性 分 支 之 后 这 也 许 是 最 简 单 的 工 作 流 了, 但 是 当 项 目 更 大, 或 更 稳 定, 你 对 自 己 所 引 入 的 工 作 更 加 在 意 时, 它 可 能 会 带 来 问 题 如 果 你 的 项 目 非 常 重 要, 你 可 能 会 使 用 两 阶 段 合 并 循 环 在 这 种 情 况 下, 你 会 维 护 两 个 长 期 分 支, 分 别 是 master 和 develop,master 分 支 只 会 在 一 个 非 常 稳 定 的 版 本 发 布 时 才 会 更 新, 而 所 有 的 新 代 码 会 首 先 整 合 进 入 develop 分 支 你 定 期 将 这 两 个 分 支 推 送 到 公 共 版 本 库 中 每 次 需 要 合 并 新 的 特 性 分 支 时 ( 合 并 特 性 分 支 前 ), 你 都 应 该 合 并 进 入 develop 分 支 ( 合 并 特 性 分 支 后 ); 当 打 标 签 发 布 的 时 候, 你 会 将 master 分 支 快 进 到 已 经 稳 定 的 develop 分 支 ( 一 次 发 布 之 后 ) Figure 75. 合 并 特 性 分 支 前

151 Figure 76. 合 并 特 性 分 支 后 Figure 77. 一 次 发 布 之 后 这 样 当 人 们 克 隆 你 项 目 的 版 本 库 后, 既 可 以 检 出 master 分 支 以 构 建 最 新 的 稳 定 版 本 并 保 持 更 新, 也 可 以 检 出 包 含 更 多 新 东 西 的 develop 分 支 你 也 可 以 扩 展 这 个 概 念, 维 护 一 个 将 所 有 工 作 合 并 到 一 起 的 整 合 分 支 当 该 分 支 的 代 码 稳 定 并 通 过 测 试 之 后, 将 其 合 并 进 入 develop 分 支 ; 经 过 一 段 时 间, 确 认 其 稳 定 之 后, 将 其 以 快 进 的 形 式 并 入 master 分 支 大 项 目 合 并 工 作 流 Git 项 目 包 含 四 个 长 期 分 支 :master next, 用 于 新 工 作 的 pu(proposed updates) 和 用 于 维 护 性 向 后 移 植 工 作 (maintenance backports) 的 maint 分 支 贡 献 者 的 新 工 作 会 以 类 似 之 前 所 介 绍 的 方 式 收 入 特 性 分 支 中 ( 见 管 理 复 杂 的 一 系 列 接 收 贡 献 的 平 行 特 性 分 支 ) 之 后 对 特 性 分 支 进 行 测 试 评 估, 检 查 其 是 否 已 经 能 够 合 并, 或 者 仍 需 要 更 多 工 作 安 全 的 特 性 分 支 会 被 合 并 入 next 分 支, 之 后 该 分 支 会 被 推 送 使 得 所 有 人 都 可 以 尝 试 整 合 到 一 起 的 特 性

152 Figure 78. 管 理 复 杂 的 一 系 列 接 收 贡 献 的 平 行 特 性 分 支 如 果 特 性 分 支 需 要 更 多 工 作, 它 则 会 被 并 入 pu 分 支 当 它 们 完 全 稳 定 之 后, 会 被 再 次 并 入 master 分 支 这 意 味 着 master 分 支 始 终 在 进 行 快 进,next 分 支 偶 尔 会 被 变 基, 而 pu 分 支 的 变 基 比 较 频 繁 : Figure 79. 将 贡 献 的 特 性 分 支 并 入 长 期 整 合 分 支

153 当 特 性 分 支 最 终 被 并 入 master 分 支 后, 便 会 被 从 版 本 库 中 删 除 掉 Git 项 目 还 有 一 个 从 上 一 次 发 布 中 派 生 出 来 的 maint 分 支 来 提 供 向 后 移 植 过 来 的 补 丁 以 供 发 布 维 护 更 新 因 此, 当 你 克 隆 Git 的 版 本 库 之 后, 就 会 有 四 个 可 分 别 评 估 该 项 目 开 发 的 不 同 阶 段 的 可 检 出 的 分 支, 检 出 哪 个 分 支, 取 决 于 你 需 要 多 新 的 版 本, 或 者 你 想 要 如 何 进 行 贡 献 ; 对 于 维 护 者 来 说, 这 套 结 构 化 的 工 作 流 能 帮 助 它 们 审 查 新 的 贡 献 变 基 与 拣 选 工 作 流 为 了 保 持 线 性 的 提 交 历 史, 有 些 维 护 者 更 喜 欢 在 master 分 支 上 对 贡 献 过 来 的 工 作 进 行 变 基 和 拣 选, 而 不 是 直 接 将 其 合 并 当 你 完 成 了 某 个 特 性 分 支 中 的 工 作, 并 且 决 定 要 将 其 整 合 的 时 候, 你 可 以 在 该 分 支 中 运 行 变 基 命 令, 在 当 前 master 分 支 ( 或 者 是 develop 等 分 支 ) 的 基 础 上 重 新 构 造 修 改 如 果 结 果 理 想 的 话, 你 可 以 快 进 master 分 支, 最 后 得 到 一 个 线 性 的 项 目 提 交 历 史 另 一 种 将 引 入 的 工 作 转 移 到 其 他 分 支 的 方 法 是 拣 选 Git 中 的 拣 选 类 似 于 对 特 定 的 某 次 提 交 的 变 基 它 会 提 取 该 提 交 的 补 丁, 之 后 尝 试 将 其 重 新 应 用 到 当 前 分 支 上 这 种 方 式 在 你 只 想 引 入 特 性 分 支 中 的 某 个 提 交, 或 者 特 性 分 支 中 只 有 一 个 提 交, 而 你 不 想 运 行 变 基 时 很 有 用 举 个 例 子, 假 设 你 的 项 目 提 交 历 史 类 似 : Figure 80. 拣 选 之 前 的 示 例 历 史 如 果 你 希 望 将 提 交 e43a6 拉 取 到 master 分 支, 你 可 以 运 行 : $ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf Finished one cherry-pick. [master]: created a0a41a9: "More friendly message when locking the index fails." 3 files changed, 17 insertions(+), 3 deletions(-) 这 样 会 拉 取 和 e43a6 相 同 的 更 改, 但 是 因 为 应 用 的 日 期 不 同, 你 会 得 到 一 个 新 的 提 交 SHA-1 值 现 在 你 的 历 史 会 变 成 这 样 :

154 Figure 81. 拣 选 特 性 分 支 中 的 一 个 提 交 后 的 历 史 现 在 你 可 以 删 除 这 个 特 性 分 支, 并 丢 弃 不 想 拉 入 的 提 交 Rerere 如 果 你 在 进 行 大 量 的 合 并 或 变 基, 或 维 护 一 个 长 期 的 特 性 分 支,Git 提 供 的 一 个 叫 做 rerere 的 功 能 会 有 一 些 帮 助 Rerere 是 重 用 已 记 录 的 冲 突 解 决 方 案 (reuse recorded resolution) 的 意 思 它 是 一 种 简 化 冲 突 解 决 的 方 法 当 启 用 rerere 时,Git 将 会 维 护 一 些 成 功 合 并 之 前 和 之 后 的 镜 像, 当 Git 发 现 之 前 已 经 修 复 过 类 似 的 冲 突 时, 便 会 使 用 之 前 的 修 复 方 案, 而 不 需 要 你 的 干 预 这 个 功 能 包 含 两 个 部 分 : 一 个 配 置 选 项 和 一 个 命 令 其 中 的 配 置 选 项 是 rerere.enabled, 把 它 放 在 全 局 配 置 中 就 可 以 了 : $ git config --global rerere.enabled true 现 在 每 当 你 进 行 一 次 需 要 解 决 冲 突 的 合 并 时, 解 决 方 案 都 会 被 记 录 在 缓 存 中, 以 备 之 后 使 用 如 果 你 需 要 和 rerere 的 缓 存 交 互, 你 可 以 使 用 git rerere 命 令 当 单 独 调 用 它 时,Git 会 检 查 解 决 方 案 数 据 库, 尝 试 寻 找 一 个 和 当 前 任 一 冲 突 相 关 的 匹 配 项 并 解 决 冲 突 ( 尽 管 当 rerere.enabled 被 设 置 为 true 时 会 自 动 进 行 ) 它 也 有 若 干 子 命 令, 可 用 来 查 看 记 录 项, 删 除 特 定 解 决 方 案 和 清 除 缓 存 全 部 内 容 等 我 们 将 在 Rerere 中 详 细 探 讨 为 发 布 打 标 签 当 你 决 定 进 行 一 次 发 布 时, 你 可 能 想 要 留 下 一 个 标 签, 这 样 在 之 后 的 任 何 一 个 提 交 点 都 可 以 重 新 创 建 该 发 布 你 在 Git 基 础 中 已 经 了 解 了 创 建 新 标 签 的 过 程 作 为 一 个 维 护 者, 如 果 你 决 定 要 为 标 签 签 名 的 话, 打 标 签 的 过 程 应

155 该 是 这 样 子 的 : $ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Scott Chacon <schacon@gmail.com>" 1024-bit DSA key, ID F721C45A, created 如 果 你 为 标 签 签 名 了, 你 可 能 会 遇 到 分 发 用 来 签 名 的 PGP 公 钥 的 问 题 Git 项 目 的 维 护 者 已 经 解 决 了 这 一 问 题, 其 方 法 是 在 版 本 库 中 以 blob 对 象 的 形 式 包 含 他 们 的 公 钥, 并 添 加 一 个 直 接 指 向 该 内 容 的 标 签 要 完 成 这 一 任 务, 首 先 你 可 以 通 过 运 行 gpg --list-keys 找 出 你 所 想 要 的 key: $ gpg --list-keys /Users/schacon/.gnupg/pubring.gpg pub 1024D/F721C45A [expires: ] uid Scott Chacon <schacon@gmail.com> sub 2048g/45D [expires: ] 之 后 你 可 以 通 过 导 出 key 并 通 过 管 道 传 递 给 git hash-object 来 直 接 将 key 导 入 到 Git 的 数 据 库 中,git hash-object 命 令 会 向 Git 中 写 入 一 个 包 含 其 内 容 的 新 blob 对 象, 并 向 你 返 回 该 blob 对 象 的 SHA-1 值 : $ gpg -a --export F721C45A git hash-object -w --stdin 659ef797d181633c87ec71ac3f9ba29fe5775b92 既 然 Git 中 已 经 包 含 你 的 key 的 内 容 了, 你 就 可 以 通 过 指 定 由 hash-object 命 令 给 出 的 新 SHA-1 值 来 创 建 一 个 直 接 指 向 它 的 标 签 : $ git tag -a maintainer-pgp-pub 659ef797d181633c87ec71ac3f9ba29fe5775b92 如 果 你 运 行 git push --tags 命 令, 那 么 maintainer-pgp-pub 标 签 将 会 被 共 享 给 所 有 人 需 要 校 验 标 签 的 人 可 以 通 过 从 数 据 库 中 直 接 拉 取 blob 对 象 并 导 入 到 GPG 中 来 导 入 PGP key: $ git show maintainer-pgp-pub gpg --import 人 们 可 以 使 用 这 个 key 来 校 验 所 有 由 你 签 名 的 标 签 另 外, 如 果 你 在 标 签 信 息 中 包 含 了 一 些 操 作 说 明, 用 户 可 以 通 过 运 行 git show <tag> 来 获 取 更 多 关 于 标 签 校 验 的 说 明

156 生 成 一 个 构 建 号 Git 中 不 存 在 随 每 次 提 交 递 增 的 v123 之 类 的 数 字 序 列, 如 果 你 想 要 为 提 交 附 上 一 个 可 读 的 名 称, 可 以 对 其 运 行 git describe 命 令 Git 将 会 给 出 一 个 字 符 串, 它 由 最 近 的 标 签 名 自 该 标 签 之 后 的 提 交 数 目 和 你 所 描 述 的 提 交 的 部 分 SHA-1 值 构 成 : $ git describe master v1.6.2-rc1-20-g8c5b85c 这 样 你 在 导 出 一 个 快 照 或 构 建 时, 可 以 给 出 一 个 便 于 人 们 理 解 的 命 名 实 际 上, 如 果 你 的 Git 是 从 Git 自 己 的 版 本 库 克 隆 下 来 并 构 建 的, 那 么 git --version 命 令 给 出 的 结 果 是 与 此 类 似 的 如 果 你 所 描 述 的 提 交 自 身 就 有 一 个 标 签, 那 么 它 将 只 会 输 出 标 签 名, 没 有 后 面 两 项 信 息 注 意 git describe 命 令 只 适 用 于 有 注 解 的 标 签 ( 即 使 用 -a 或 -s 选 项 创 建 的 标 签 ), 所 以 如 果 你 在 使 用 git describe 命 令 的 话, 为 了 确 保 能 为 标 签 生 成 合 适 的 名 称, 打 发 布 标 签 时 都 应 该 采 用 加 注 解 的 方 式 你 也 可 以 使 用 这 个 字 符 串 来 调 用 checkout 或 show 命 令, 但 是 这 依 赖 于 其 末 尾 的 简 短 SHA-1 值, 因 此 不 一 定 一 直 有 效 比 如, 最 近 Linux 内 核 为 了 保 证 SHA-1 值 对 象 的 唯 一 性, 将 其 位 数 由 8 位 扩 展 到 了 10 位, 导 致 以 前 的 git describe 输 出 全 部 失 效 准 备 一 次 发 布 现 在 你 可 以 发 布 一 个 构 建 了 其 中 一 件 事 情 就 是 为 那 些 不 使 用 Git 的 可 怜 包 们 创 建 一 个 最 新 的 快 照 归 档 使 用 git archive 命 令 完 成 此 工 作 : $ git archive master --prefix='project/' gzip > `git describe master`.tar.gz $ ls *.tar.gz v1.6.2-rc1-20-g8c5b85c.tar.gz 如 果 有 人 将 这 个 压 缩 包 解 压, 他 就 可 以 得 到 你 的 项 目 文 件 夹 的 最 新 快 照 你 也 可 以 以 类 似 的 方 式 创 建 一 个 zip 压 缩 包, 但 此 时 你 应 该 向 git archive 命 令 传 递 --format=zip 选 项 : $ git archive master --prefix='project/' --format=zip > `git describe master`.zip 现 在 你 有 了 本 次 发 布 的 一 个 tar 包 和 一 个 zip 包, 可 以 将 其 上 传 到 网 站 或 以 电 子 邮 件 的 形 式 发 送 给 人 们 制 作 提 交 简 报 现 在 是 时 候 通 知 邮 件 列 表 里 那 些 好 奇 你 的 项 目 发 生 了 什 么 的 人 了 使 用 git shortlog 命 令 可 以 快 速 生 成 一 份 包 含 从 上 次 发 布 之 后 项 目 新 增 内 容 的 修 改 日 志 (changelog) 类 文 档 它 会 对 你 给 定 范 围 内 的 所 有 提 交 进 行 总 结 ; 比 如, 你 的 上 一 次 发 布 名 称 是 v1.0.1, 那 么 下 面 的 命 令 可 以 给 出 上 次 发 布 以 来 所 有 提 交 的 总 结 :

157 $ git shortlog --no-merges master --not v1.0.1 Chris Wanstrath (8): Add support for annotated tags to Grit::Tag Add packed-refs annotated tag support. Add Grit::Commit#to_patch Update version and History.txt Remove stray `puts` Make ls_tree ignore nils Tom Preston-Werner (4): fix dates in history dynamic version method Version bump to Regenerated gemspec for version 这 份 整 洁 的 总 结 包 括 了 自 v1.0.1 以 来 的 所 有 提 交, 并 且 已 经 按 照 作 者 分 好 组, 你 可 以 通 过 电 子 邮 件 将 其 直 接 发 送 到 列 表 中 总 结 你 现 在 能 自 如 地 使 用 Git 为 项 目 做 出 贡 献 维 护 自 己 的 项 目 或 采 纳 其 他 用 户 的 贡 献 了 恭 喜 你 成 为 了 一 个 高 效 的 Git 开 发 者! 下 一 章 中, 你 将 会 学 到 如 何 使 用 规 模 最 大 最 流 行 的 Git 托 管 服 务,GitHub

158 GitHub GitHub 是 最 大 的 Git 版 本 库 托 管 商, 是 成 千 上 万 的 开 发 者 和 项 目 能 够 合 作 进 行 的 中 心 大 部 分 Git 版 本 库 都 托 管 在 GitHub, 很 多 开 源 项 目 使 用 GitHub 实 现 Git 托 管 问 题 追 踪 代 码 审 查 以 及 其 它 事 情 所 以, 尽 管 这 不 是 Git 开 源 项 目 的 直 接 部 分, 但 如 果 想 要 专 业 地 使 用 Git, 你 将 不 可 避 免 地 与 GitHub 打 交 道, 所 以 这 依 然 是 一 个 绝 好 的 学 习 机 会 本 章 将 讨 论 如 何 高 效 地 使 用 GitHub 我 们 将 学 习 如 何 注 册 和 管 理 账 户 创 建 和 使 用 Git 版 本 库 向 已 有 项 目 贡 献 的 通 用 流 程 以 及 如 何 接 受 别 人 向 你 自 己 项 目 的 贡 献 GitHub 的 编 程 接 口 和 很 多 能 够 让 这 些 操 作 更 简 单 的 小 提 示 如 果 你 对 如 何 使 用 GitHub 托 管 自 己 的 项 目, 或 者 与 已 经 托 管 在 GitHub 上 面 的 项 目 进 行 合 作 没 有 兴 趣, 可 以 直 接 跳 到 Git 工 具 这 一 章 WARNING 接 口 的 改 变 需 要 注 意 一 点, 同 很 多 活 跃 的 网 站 一 样, 书 中 截 取 的 界 面 会 随 时 间 而 改 变 希 望 我 们 试 图 表 达 的 核 心 思 想 一 直 是 不 变 的, 但 是, 如 果 你 想 要 这 些 截 图 的 更 新 版 本, 本 书 的 在 线 版 本 或 许 有 更 新 的 截 图 账 户 的 创 建 和 配 置 你 所 需 要 做 的 第 一 件 事 是 创 建 一 个 免 费 账 户 直 接 访 问 选 择 一 个 未 被 占 用 的 用 户 名, 提 供 一 个 电 子 邮 件 地 址 和 密 码, 点 击 写 着 ` Sign up for GitHub ' 的 绿 色 大 按 钮 即 可

159 Figure 82. GitHub 注 册 表 单 你 将 看 到 的 下 一 个 页 面 是 升 级 计 划 的 价 格 页 面, 目 前 我 们 可 以 直 接 忽 略 这 个 页 面 GitHub 会 给 你 提 供 的 邮 件 地 址 发 送 一 封 验 证 邮 件 尽 快 到 你 的 邮 箱 进 行 验 证, 这 是 非 常 重 要 的 ( 我 们 会 在 后 面 了 解 到 这 点 ) NOTE GitHub 为 免 费 账 户 提 供 了 完 整 功 能, 限 制 是 你 的 项 目 都 将 被 完 全 公 开 ( 每 个 人 都 具 有 读 权 限 ) GitHub 的 付 费 计 划 可 以 让 你 拥 有 一 定 数 目 的 私 有 项 目, 不 过 本 书 将 不 涉 及 这 部 分 内 容 点 击 屏 幕 左 上 角 的 Octocat 图 标, 你 将 来 到 控 制 面 板 页 面 现 在, 你 已 经 做 好 了 使 用 GitHub 的 准 备 工 作 SSH 访 问 现 在, 你 完 全 可 以 使 用 协 议, 通 过 你 刚 刚 创 建 的 用 户 名 和 密 码 访 问 Git 版 本 库 但 是, 如 果 仅 仅 克 隆 公 有 项 目, 你 甚 至 不 需 要 注 册 刚 刚 我 们 创 建 的 账 户 是 为 了 以 后 fork 其 它 项 目, 以 及 推 送 我 们 自 己 的 修 改 如 果 你 习 惯 使 用 SSH 远 程, 你 需 要 配 置 一 个 公 钥 ( 如 果 你 没 有 公 钥, 参 考 生 成 SSH 公 钥 ) 使 用 窗 口 右 上 角 的 链 接 打 开 你 的 账 户 设 置 :

160 Figure 83. `Account settings ' 链 接 然 后 在 左 侧 选 择 ` SSH keys ' 部 分 Figure 84. `SSH keys ' 链 接 在 这 个 页 面 点 击 Add an SSH key 按 钮, 给 你 的 公 钥 起 一 个 名 字, 将 你 的 `~/.ssh/id_rsa.pub`( 或 者 自 定 义 的 其 它 名 字 ) 公 钥 文 件 的 内 容 粘 贴 到 文 本 区, 然 后 点 击 ` Add key ' NOTE 确 保 给 你 的 SSH 密 钥 起 一 个 能 够 记 得 住 的 名 字 你 可 以 为 每 一 个 密 钥 起 名 字 ( 例 如, 我 的 笔 记 本 电 脑 或 者 工 作 账 户 等 ), 以 便 以 后 需 要 吊 销 密 钥 时 能 够 方 便 地 区 分 头 像 下 一 步, 如 果 愿 意 的 话, 你 可 以 将 生 成 的 头 像 换 成 你 喜 欢 的 图 片 首 先, 来 到 ` Profile 标 签 页 ( 在 ` SSH Keys' 标 签 页 上 方 ), 点 击 ``Upload new picture '

161 Figure 85. `Profile ' 链 接 我 们 选 择 了 本 地 磁 盘 上 的 一 个 Git 图 标, 上 传 之 后 还 可 以 对 其 进 行 裁 剪 Figure 86. 裁 剪 头 像

162 现 在, 在 网 站 任 意 有 你 参 与 的 位 置, 人 们 都 可 以 在 你 的 用 户 名 旁 边 看 到 你 的 头 像 如 果 你 已 经 把 头 像 上 传 到 了 流 行 的 Gravatar 托 管 服 务 (Wordpress 账 户 经 常 使 用 ), 默 认 就 会 使 用 这 个 头 像, 因 此, 你 就 不 需 要 进 行 这 一 步 骤 了 邮 件 地 址 GitHub 使 用 用 户 邮 件 地 址 区 分 Git 提 交 如 果 你 在 自 己 的 提 交 中 使 用 了 多 个 邮 件 地 址, 希 望 GitHub 可 以 正 确 地 将 它 们 连 接 起 来, 你 需 要 在 管 理 页 面 的 s 部 分 添 加 你 拥 有 的 所 有 邮 箱 地 址 Figure 87. 添 加 邮 件 地 址 在 添 加 邮 件 地 址 中 我 们 可 以 看 到 一 些 不 同 的 状 态 顶 部 的 地 址 是 通 过 验 证 的, 并 且 被 设 置 为 主 要 地 址, 这 意 味 着 该 地 址 会 接 收 到 所 有 的 通 知 和 回 复 第 二 个 地 址 是 通 过 验 证 的, 如 果 愿 意 的 话, 可 以 将 其 设 置 为 主 要 地 址 最 后 一 个 地 址 是 未 通 过 验 证 的, 这 意 味 着 你 不 能 将 其 设 置 为 主 要 地 址 当 GitHub 发 现 任 意 版 本 库 中 的 任 意 提 交 信 息 包 含 了 这 些 地 址, 它 就 会 将 其 链 接 到 你 的 账 户 两 步 验 证 最 后, 为 了 额 外 的 安 全 性, 你 绝 对 应 当 设 置 两 步 验 证, 简 写 为 2FA 两 步 验 证 是 一 种 用 于 降 低 因 你 的 密 码 被 盗 而 带 来 的 账 户 风 险 的 验 证 机 制, 现 在 已 经 变 得 越 来 越 流 行 开 启 两 步 验 证,GitHub 会 要 求 你 用 两 种 不 同 的 验 证 方 法, 这 样, 即 使 其 中 一 个 被 攻 破, 攻 击 者 也 不 能 访 问 你 的 账 户 你 可 以 在 Account settings 页 面 的 Security 标 签 页 中 找 到 Two-factor Authentication 设 置

163 Figure 88. Security 标 签 页 中 的 2FA 点 击 ` Set up two-factor authentication 按 钮, 会 跳 转 到 设 置 页 面 该 页 面 允 许 你 选 择 是 要 在 登 录 时 使 用 手 机 app 生 成 辅 助 码 ( 一 种 ` 基 于 时 间 的 一 次 性 密 码 ), 还 是 要 GitHub 通 过 SMS 发 送 辅 助 码 选 择 合 适 的 方 法 后, 按 照 提 示 步 骤 设 置 2FA, 你 的 账 户 会 变 得 更 安 全, 每 次 登 录 GitHub 时 都 需 要 提 供 除 密 码 以 外 的 辅 助 码 对 项 目 做 出 贡 献 账 户 已 经 建 立 好 了, 现 在 我 们 来 了 解 一 些 能 帮 助 你 对 现 有 的 项 目 做 出 贡 献 的 知 识 派 生 (Fork) 项 目 如 果 你 想 要 参 与 某 个 项 目, 但 是 并 没 有 推 送 权 限, 这 时 可 以 对 这 个 项 目 进 行 派 生 派 生 的 意 思 是 指,GitHub 将 在 你 的 空 间 中 创 建 一 个 完 全 属 于 你 的 项 目 副 本, 且 你 对 其 具 有 推 送 权 限 NOTE 在 以 前, fork 是 一 个 贬 义 词, 指 的 是 某 个 人 使 开 源 项 目 向 不 同 的 方 向 发 展, 或 者 创 建 一 个 竞 争 项 目, 使 得 原 项 目 的 贡 献 者 分 裂 在 GitHub, fork 指 的 是 你 自 己 的 空 间 中 创 建 的 项 目 副 本, 这 个 副 本 允 许 你 以 一 种 更 开 放 的 方 式 对 其 进 行 修 改 通 过 这 种 方 式, 项 目 的 管 理 者 不 再 需 要 忙 着 把 用 户 添 加 到 贡 献 者 列 表 并 给 予 他 们 推 送 权 限 人 们 可 以 派 生 这 个 项 目, 将 修 改 推 送 到 派 生 出 的 项 目 副 本 中, 并 通 过 创 建 合 并 请 求 (Pull Request) 来 让 他 们 的 改 动 进 入 源 版 本 库, 下 文 我 们 会 详 细 说 明 创 建 了 合 并 请 求 后, 就 会 开 启 一 个 可 供 审 查 代 码 的 板 块, 项 目 的 拥 有 者 和 贡 献 者 可 以 在 此 讨 论 相 关 修 改, 直 到 项 目 拥 有 者 对 其 感 到 满 意, 并 且 认 为 这 些 修 改 可 以 被 合 并 到 版 本 库 你 可 以 通 过 点 击 项 目 页 面 右 上 角 的 Fork 按 钮, 来 派 生 这 个 项 目

164 Figure 89. Fork 按 钮 稍 等 片 刻, 你 将 被 转 到 新 项 目 页 面, 该 项 目 包 含 可 写 的 代 码 副 本 GitHub 流 程 GitHub 设 计 了 一 个 以 合 并 请 求 为 中 心 的 特 殊 合 作 流 程 它 基 于 我 们 在 Git 分 支 的 特 性 分 支 中 提 到 的 工 作 流 程 不 管 你 是 在 一 个 紧 密 的 团 队 中 使 用 单 独 的 版 本 库, 或 者 使 用 许 多 的 Fork 来 为 一 个 由 陌 生 人 组 成 的 国 际 企 业 或 网 络 做 出 贡 献, 这 种 合 作 流 程 都 能 应 付 流 程 通 常 如 下 : 1. 从 master 分 支 中 创 建 一 个 新 分 支 2. 提 交 一 些 修 改 来 改 进 项 目 3. 将 这 个 分 支 推 送 到 GitHub 上 4. 创 建 一 个 合 并 请 求 5. 讨 论, 根 据 实 际 情 况 继 续 修 改 6. 项 目 的 拥 有 者 合 并 或 关 闭 你 的 合 并 请 求 这 基 本 和 集 成 管 理 者 工 作 流 中 的 一 体 化 管 理 流 程 差 不 多, 但 是 团 队 可 以 使 用 GitHub 提 供 的 网 页 工 具 替 代 电 子 邮 件 来 交 流 和 审 查 修 改 现 在 我 们 来 看 一 个 使 用 这 个 流 程 的 例 子 创 建 合 并 请 求 Tony 在 找 一 些 能 在 他 的 Arduino 微 控 制 器 上 运 行 的 代 码, 他 觉 得 中 的 代 码 不 错

165 Figure 90. 他 想 要 做 出 贡 献 的 项 目 但 是 有 个 问 题, 这 个 代 码 中 的 的 闪 烁 频 率 太 高, 我 们 觉 得 3 秒 一 次 比 1 秒 一 次 更 好 一 些 所 以 让 我 们 来 改 进 这 个 程 序, 并 将 修 改 后 的 代 码 提 交 给 这 个 项 目 首 先, 单 击 Fork 按 钮 来 获 得 这 个 项 目 的 副 本 我 们 使 用 的 用 户 名 是 tonychacon, 所 以 这 个 项 目 副 本 的 访 问 地 址 是 : 我 们 将 它 克 隆 到 本 地, 创 建 一 个 分 支, 修 改 代 码, 最 后 再 将 改 动 推 送 到 GitHub

166 $ git clone 1 Cloning into 'blink'... $ cd blink $ git checkout -b slow-blink 2 Switched to a new branch 'slow-blink' $ sed -i '' 's/1000/3000/' blink.ino 3 $ git diff --word-diff 4 diff --git a/blink.ino b/blink.ino index 15b9911..a6cc5a a/blink.ino ,7 void setup() { // the loop routine runs over and over again forever: void loop() { digitalwrite(led, HIGH); // turn the LED on (HIGH is the voltage level) [-delay(1000);-]{+delay(3000);+} // wait for a second digitalwrite(led, LOW); // turn the LED off by making the voltage LOW [-delay(1000);-]{+delay(3000);+} // wait for a second } $ git commit -a -m 'three seconds is better' 5 [slow-blink 5ca509d] three seconds is better 1 file changed, 2 insertions(+), 2 deletions(-) $ git push origin slow-blink 6 Username for ' tonychacon Password for ' Counting objects: 5, done. Delta compression using up to 8 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 340 bytes 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) To * [new branch] slow-blink -> slow-blink 1 将 派 生 出 的 副 本 克 隆 到 本 地 2 创 建 出 名 称 有 意 义 的 分 支 3 修 改 代 码 4 检 查 改 动 5 将 改 动 提 交 到 分 支 中 6 将 新 分 支 推 送 到 GitHub 的 副 本 中

167 现 在 到 GitHub 上 查 看 之 前 的 项 目 副 本, 可 以 看 到 GitHub 提 示 我 们 有 新 的 分 支, 并 且 显 示 了 一 个 大 大 的 绿 色 按 钮 让 我 们 可 以 检 查 我 们 的 改 动, 并 给 源 项 目 创 建 合 并 请 求 你 也 可 以 到 Branches ( 分 支 ) 页 面 查 看 分 支 并 创 建 合 并 请 求 : 用 户 名 >/< 项 目 名 >/branches Figure 91. 合 并 请 求 按 钮 如 果 你 点 击 了 那 个 绿 色 按 钮, 就 会 看 到 一 个 新 页 面, 在 这 里 我 们 可 以 对 改 动 填 写 标 题 和 描 述, 让 项 目 的 拥 有 者 考 虑 一 下 我 们 的 改 动 通 常 花 点 时 间 来 编 写 个 清 晰 有 用 的 描 述 是 个 不 错 的 主 意, 这 能 让 作 者 明 白 为 什 么 这 个 改 动 可 以 给 他 的 项 目 带 来 好 处, 并 且 让 他 接 受 合 并 请 求 同 时 我 们 也 能 看 到 比 主 分 支 中 所 领 先 (ahead) 的 提 交 ( 在 这 个 例 子 中 只 有 一 个 ) 以 及 所 有 将 会 被 合 并 的 改 动 与 之 前 代 码 的 对 比

168 Figure 92. 合 并 请 求 创 建 页 面 当 你 单 击 了 Create pull request ( 创 建 合 并 请 求 ) 的 按 钮 后, 这 个 项 目 的 拥 有 者 将 会 收 到 一 条 包 含 关 改 动 和 合 并 请 求 页 面 的 链 接 的 提 醒 NOTE 虽 然 合 并 请 求 通 常 是 在 贡 献 者 准 备 好 在 公 开 项 目 中 提 交 改 动 的 时 候 提 交, 但 是 也 常 被 用 在 仍 处 于 开 发 阶 段 的 内 部 项 目 中 因 为 合 并 请 求 在 提 交 后 依 然 可 以 加 入 新 的 改 动, 它 也 经 常 被 用 来 建 立 团 队 合 作 的 环 境, 而 不 只 是 在 最 终 阶 段 使 用 利 用 合 并 请 求 现 在, 项 目 的 拥 有 者 可 以 看 到 你 的 改 动 并 合 并 它, 拒 绝 它 或 是 发 表 评 论 在 这 里 我 们 就 当 作 他 喜 欢 这 个 点 子, 但 是 他 想 要 让 灯 熄 灭 的 时 间 比 点 亮 的 时 间 稍 长 一 些 接 下 来 可 能 会 通 过 电 子 邮 件 进 行 互 动, 就 像 我 们 在 分 布 式 Git 中 提 到 的 工 作 流 程 那 样, 但 是 在 GitHub, 这 些 都

169 在 线 上 完 成 项 目 的 拥 有 者 可 以 审 查 修 改, 只 需 要 单 击 某 一 行, 就 可 以 对 其 发 表 评 论 Figure 93. 对 合 并 请 求 内 的 特 定 一 行 发 表 评 论 当 维 护 者 发 表 评 论 后, 提 交 合 并 请 求 的 人, 以 及 所 有 正 在 关 注 (Watching) 这 个 版 本 库 的 用 户 都 会 收 到 通 知 我 们 待 会 儿 将 会 告 诉 你 如 何 修 改 这 项 设 置 现 在, 如 果 Tony 有 开 启 电 子 邮 件 提 醒, 他 将 会 收 到 这 样 的 一 封 邮 件 : Figure 94. 通 过 电 子 邮 件 发 送 的 评 论 提 醒 每 个 人 都 能 在 合 并 请 求 中 发 表 评 论 在 合 并 请 求 讨 论 页 面 里 我 们 可 以 看 到 项 目 拥 有 者 对 某 行 代 码 发 表 评 论, 并 在 讨 论 区 留 下 了 一 个 普 通 评 论 你 可 以 看 到 被 评 论 的 代 码 也 会 在 互 动 中 显 示 出 来

170 Figure 95. 合 并 请 求 讨 论 页 面 现 在 贡 献 者 可 以 看 到 如 何 做 才 能 让 他 们 的 改 动 被 接 受 幸 运 的 是, 这 也 是 一 件 轻 松 的 事 情 如 果 你 使 用 的 是 电 子 邮 件 进 行 交 流, 你 需 要 再 次 对 代 码 进 行 修 改 并 重 新 提 交 至 邮 件 列 表, 在 GitHub 上, 你 只 需 要 再 次 提 交 到 你 的 分 支 中 并 推 送 即 可 如 果 贡 献 者 完 成 了 以 上 的 操 作, 项 目 的 拥 有 者 会 再 次 收 到 提 醒, 当 他 们 查 看 页 面 时, 将 会 看 到 最 新 的 改 动 事 实 上, 只 要 提 交 中 有 一 行 代 码 改 动,GitHub 都 会 注 意 到 并 处 理 掉 旧 的 变 更 集

171 Figure 96. 最 终 的 合 并 请 求 如 果 你 点 开 合 并 请 求 的 Files Changed ( 更 改 的 文 件 ) 选 项 卡, 你 将 会 看 到 整 理 过 的 差 异 表 也 就 是 这 个 分 支 被 合 并 到 主 分 支 之 后 将 会 产 生 的 所 有 改 动, 其 实 就 是 git diff master...< 分 支 名 > 命 令 的 执 行 结 果 你 可 以 浏 览 确 定 引 入 了 哪 些 东 西 来 了 解 更 多 关 于 差 异 表 的 知 识 你 还 会 注 意 到,GitHub 会 检 查 你 的 合 并 请 求 是 否 能 直 接 合 并, 如 果 可 以, 将 会 提 供 一 个 按 钮 来 进 行 合 并 操 作 这 个 按 钮 只 在 你 对 版 本 库 有 写 入 权 限 并 且 可 以 进 行 简 洁 合 并 时 才 会 显 示 你 点 击 后 GitHub 将 做 出 一 个 非 快 进 式 (non-fast-forward) 合 并, 即 使 这 个 合 并 能 够 快 进 式 (fast-forward) 合 并,GitHub 依 然 会 创 建 一 个 合 并 提 交

172 如 果 你 需 要, 你 还 可 以 将 分 支 拉 取 并 在 本 地 合 并 如 果 你 将 这 个 分 支 合 并 到 master 分 支 中 并 推 送 到 GitHub, 这 个 合 并 请 求 会 被 自 动 关 闭 这 就 是 大 部 分 GitHub 项 目 使 用 的 工 作 流 程 创 建 分 支, 基 于 分 支 创 建 合 并 请 求, 进 行 讨 论, 根 据 需 要 继 续 在 分 支 上 进 行 修 改, 最 终 关 闭 或 合 并 合 并 请 求 不 必 总 是 Fork NOTE 有 件 很 重 要 的 事 情 : 你 可 以 在 同 一 个 版 本 库 中 不 同 的 分 支 提 交 合 并 请 求 如 果 你 正 在 和 某 人 实 现 某 个 功 能, 而 且 你 对 项 目 有 写 权 限, 你 可 以 推 送 分 支 到 版 本 库, 并 在 master 分 支 提 交 一 个 合 并 请 求 并 在 此 进 行 代 码 审 查 和 讨 论 的 操 作 不 需 要 进 行 Fork 合 并 请 求 的 进 阶 用 法 目 前, 我 们 学 到 了 如 何 在 GitHub 平 台 对 一 个 项 目 进 行 最 基 础 的 贡 献 现 在 我 们 会 教 给 你 一 些 小 技 巧, 让 你 可 以 更 加 有 效 率 地 使 用 合 并 请 求 将 合 并 请 求 制 作 成 补 丁 有 一 件 重 要 的 事 情 : 许 多 项 目 并 不 认 为 合 并 请 求 可 以 作 为 补 丁, 就 和 通 过 邮 件 列 表 工 作 的 的 项 目 对 补 丁 贡 献 的 看 法 一 样 大 多 数 的 GitHub 项 目 将 合 并 请 求 的 分 支 当 作 对 改 动 的 交 流 方 式, 并 将 变 更 集 合 起 来 统 一 进 行 合 并 这 是 个 重 要 的 差 异, 因 为 一 般 来 说 改 动 会 在 代 码 完 成 前 提 出, 这 和 基 于 邮 件 列 表 的 补 丁 贡 献 有 着 天 差 地 别 这 使 得 维 护 者 们 可 以 更 早 的 沟 通, 由 社 区 中 的 力 量 能 提 出 更 好 的 方 案 当 有 人 从 合 并 请 求 提 交 了 一 些 代 码, 并 且 维 护 者 和 社 区 提 出 了 一 些 意 见, 这 个 补 丁 系 列 并 不 需 要 从 头 来 过, 只 需 要 将 改 动 重 新 提 交 并 推 送 到 分 支 中, 这 使 得 讨 论 的 背 景 和 过 程 可 以 齐 头 并 进 举 个 例 子, 你 可 以 回 去 看 看 最 终 的 合 并 请 求, 你 会 注 意 到 贡 献 者 没 有 变 基 他 的 提 交 再 提 交 一 个 新 的 合 并 请 求, 而 是 直 接 增 加 了 新 的 提 交 并 推 送 到 已 有 的 分 支 中 如 果 你 之 后 再 回 去 查 看 这 个 合 并 请 求, 你 可 以 轻 松 地 找 到 这 个 修 改 的 原 因 点 击 网 页 上 的 Merge ( 合 并 ) 按 钮 后, 会 建 立 一 个 合 并 提 交 并 指 向 这 个 合 并 请 求, 你 就 可 以 很 轻 松 的 研 究 原 来 的 讨 论 内 容 与 上 游 保 持 同 步 如 果 你 的 合 并 请 求 由 于 过 时 或 其 他 原 因 不 能 干 净 地 合 并, 你 需 要 进 行 修 复 才 能 让 维 护 者 对 其 进 行 合 并 GitHub 会 对 每 个 提 交 进 行 测 试, 让 你 知 道 你 的 合 并 请 求 能 否 简 洁 的 合 并 Figure 97. 不 能 进 行 干 净 合 并 如 果 你 看 到 了 像 不 能 进 行 干 净 合 并 中 的 画 面, 你 就 需 要 修 复 你 的 分 支 让 这 个 提 示 变 成 绿 色, 这 样 维 护 者 就 不 需 要 再 做 额 外 的 工 作 你 有 两 种 方 法 来 解 决 这 个 问 题 你 可 以 把 你 的 分 支 变 基 到 目 标 分 支 中 去 ( 通 常 是 你 派 生 出 的 版 本 库 中 的 master

173 分 支 ), 或 者 你 可 以 合 并 目 标 分 支 到 你 的 分 支 中 去 GitHub 上 的 大 多 数 的 开 发 者 会 使 用 后 一 种 方 法, 基 于 我 们 在 上 一 节 提 到 的 理 由 : 我 们 最 看 重 的 是 历 史 记 录 和 最 后 的 合 并, 变 基 除 了 给 你 带 来 看 上 去 简 洁 的 历 史 记 录, 只 会 让 你 的 工 作 变 得 更 加 困 难 且 更 容 易 犯 错 如 果 你 想 要 合 并 目 标 分 支 来 让 你 的 合 并 请 求 变 得 可 合 并, 你 需 要 将 源 版 本 库 添 加 为 一 个 新 的 远 端, 并 从 远 端 抓 取 内 容, 合 并 主 分 支 的 内 容 到 你 的 分 支 中 去, 修 复 所 有 的 问 题 并 最 终 重 新 推 送 回 你 提 交 合 并 请 求 使 用 的 分 支 在 这 个 例 子 中, 我 们 再 次 使 用 之 前 的 tonychacon 用 户 来 进 行 示 范, 源 作 者 提 交 了 一 个 改 动, 使 得 合 并 请 求 和 它 产 生 了 冲 突 现 在 来 看 我 们 解 决 这 个 问 题 的 步 骤 $ git remote add upstream 1 $ git fetch upstream 2 remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. Unpacking objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0) From * [new branch] master -> upstream/master $ git merge upstream/master 3 Auto-merging blink.ino CONFLICT (content): Merge conflict in blink.ino Automatic merge failed; fix conflicts and then commit the result. $ vim blink.ino 4 $ git add blink.ino $ git commit [slow-blink 3c8d735] Merge remote-tracking branch 'upstream/master' \ into slower-blink $ git push origin slow-blink 5 Counting objects: 6, done. Delta compression using up to 8 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 682 bytes 0 bytes/s, done. Total 6 (delta 2), reused 0 (delta 0) To ef4725c..3c8d735 slower-blink -> slow-blink 1 将 源 版 本 库 添 加 为 一 个 远 端, 并 命 名 为 upstream ( 上 游 ) 2 从 远 端 抓 取 最 新 的 内 容 3 将 主 分 支 的 内 容 合 并 到 你 的 分 支 中 4 修 复 产 生 的 冲 突

174 5 再 推 送 回 同 一 个 分 支 你 完 成 了 上 面 的 步 骤 后, 合 并 请 求 将 会 自 动 更 新 并 重 新 检 查 是 否 能 干 净 的 合 并 Figure 98. 合 并 请 求 现 在 可 以 干 净 地 合 并 了 Git 的 伟 大 之 处 就 是 你 可 以 一 直 重 复 以 上 操 作 如 果 你 有 一 个 运 行 了 十 分 久 的 项 目, 你 可 以 轻 松 地 合 并 目 标 分 支 且 只 需 要 处 理 最 近 的 一 次 冲 突, 这 使 得 管 理 流 程 更 加 容 易 如 果 你 一 定 想 对 分 支 做 变 基 并 进 行 清 理, 你 可 以 这 么 做, 但 是 强 烈 建 议 你 不 要 强 行 的 提 交 到 已 经 提 交 了 合 并 请 求 的 分 支 如 果 其 他 人 拉 取 了 这 个 分 支 并 进 行 一 些 修 改, 你 将 会 遇 到 变 基 的 风 险 中 提 到 的 问 题 相 对 的, 将 变 基 后 的 分 支 推 送 到 GitHub 上 的 一 个 新 分 支 中, 并 且 创 建 一 个 全 新 的 合 并 请 求 引 用 旧 的 合 并 请 求, 然 后 关 闭 旧 的 合 并 请 求 参 考 你 的 下 个 问 题 可 能 是 我 该 如 何 引 用 旧 的 合 并 请 求? 有 许 多 方 法 可 以 让 你 在 GitHub 上 的 几 乎 任 何 地 方 引 用 其 他 东 西 先 从 如 何 对 合 并 请 求 或 议 题 (Issue) 进 行 相 互 引 用 开 始 所 有 的 合 并 请 求 和 议 题 在 项 目 中 都 会 有 一 个 独 一 无 二 的 编 号 举 个 例 子, 你 无 法 同 时 拥 有 3 号 合 并 请 求 和 3 号 议 题 如 果 你 想 要 引 用 任 何 一 个 合 并 请 求 或 议 题, 你 只 需 要 在 提 交 或 描 述 中 输 入 #< 编 号 > 即 可 你 也 可 以 指 定 引 用 其 他 版 本 库 的 议 题 或 合 并 请 求, 如 果 你 想 要 引 用 其 他 人 对 该 版 本 库 的 Fork 中 的 议 题 或 合 并 请 求, 输 入 用 户 名 #< 编 号 >, 如 果 在 不 同 的 版 本 库 中, 输 入 用 户 名 / 版 本 库 名 #< 编 号 > 我 们 来 看 一 个 例 子 假 设 我 们 对 上 个 例 子 中 的 分 支 进 行 了 变 基, 并 为 此 创 建 一 个 新 的 合 并 请 求, 现 在 我 们 希 望 能 在 新 的 合 并 请 求 中 引 用 旧 的 合 并 请 求 我 们 同 时 希 望 引 用 一 个 派 生 出 的 项 目 中 的 议 题 和 一 个 完 全 不 同 的 项 目 中 的 议 题, 就 可 以 像 在 合 并 请 求 中 的 交 叉 引 用 这 样 填 写 描 述

175 Figure 99. 在 合 并 请 求 中 的 交 叉 引 用 当 我 们 提 交 了 这 个 合 并 请 求, 我 们 将 会 看 到 以 上 内 容 被 渲 染 成 这 样 : 在 合 并 请 求 中 渲 染 后 的 交 叉 引 用 Figure 100. 在 合 并 请 求 中 渲 染 后 的 交 叉 引 用 你 会 注 意 到 完 整 的 GitHub 地 址 被 简 化 了, 只 留 下 了 必 要 的 信 息 如 果 Tony 回 去 关 闭 了 源 合 并 请 求, 我 们 可 以 看 到 一 个 被 引 用 的 提 示,GitHub 会 自 动 的 反 向 追 踪 事 件 并 显 示 在 合 并 请 求 的 时 间 轴 上 这 意 味 着 任 何 查 看 这 个 合 并 请 求 的 人 可 以 轻 松 地 访 问 新 的 合 并 请 求 这 个 链 接 就 像 在 合 并 请 求 中 渲 染 后 的 交 叉 引 用 中 展 示 的 那 样

176 Figure 101. 在 合 并 请 求 中 渲 染 后 的 交 叉 引 用 除 了 议 题 编 号 外, 你 还 可 以 通 过 使 用 提 交 的 SHA-1 来 引 用 提 交 你 必 须 完 整 的 写 出 40 位 长 的 SHA,GitHub 会 在 评 论 中 自 动 地 产 生 指 向 这 个 提 交 的 链 接 同 样 的, 你 可 以 像 引 用 议 题 一 样 对 Fork 出 的 项 目 中 的 提 交 或 者 其 他 项 目 中 的 提 交 进 行 引 用 Markdown 对 于 在 GitHub 中 绝 大 多 数 文 本 框 中 能 够 做 到 的 事, 引 用 其 他 议 题 只 是 个 开 始 在 议 题 和 合 并 请 求 的 描 述, 评 论 和 代 码 评 论 还 有 其 他 地 方, 都 可 以 使 用 GitHub 风 格 的 Markdown Markdown 可 以 让 你 输 入 纯 文 本, 但 是 渲 染 出 丰 富 的 内 容 查 看 一 个 Markdown 的 例 子 和 渲 染 效 果 里 的 例 子 来 了 解 如 何 书 写 评 论 或 文 本, 并 通 过 Markdown 进 行 渲 染 Figure 102. 一 个 Markdown 的 例 子 和 渲 染 效 果 GitHub 风 格 的 Markdown GitHub 风 格 的 Markdown 增 加 了 一 些 基 础 的 Markdown 中 做 不 到 的 东 西 它 在 创 建 合 并 请 求 和 议 题 中 的 评 论

177 和 描 述 时 十 分 有 用 任 务 列 表 第 一 个 GitHub 专 属 的 Markdown 功 能, 特 别 是 用 在 合 并 请 求 中, 就 是 任 务 列 表 一 个 任 务 列 表 可 以 展 示 出 一 系 列 你 想 要 完 成 的 事 情, 并 带 有 复 选 框 把 它 们 放 在 议 题 或 合 并 请 求 中 时, 通 常 可 以 展 示 你 想 要 完 成 的 事 情 你 可 以 这 样 创 建 一 个 任 务 列 表 : - [X] 编 写 代 码 - [ ] 编 写 所 有 测 试 程 序 - [ ] 为 代 码 编 写 文 档 如 果 我 们 将 这 个 列 表 加 入 合 并 请 求 或 议 题 的 描 述 中, 它 将 会 被 渲 染 Markdown 评 论 中 渲 染 后 的 任 务 列 表 这 样 Figure 103. Markdown 评 论 中 渲 染 后 的 任 务 列 表 在 合 并 请 求 中, 任 务 列 表 经 常 被 用 来 在 合 并 之 前 展 示 这 个 分 支 将 要 完 成 的 事 情 最 酷 的 地 方 就 是, 你 只 需 要 点 击 复 选 框, 就 能 更 新 评 论 你 不 需 要 直 接 修 改 Markdown 不 仅 如 此,GitHub 还 会 将 你 在 议 题 和 合 并 请 求 中 的 任 务 列 表 整 理 起 来 集 中 展 示 举 个 例 子, 如 果 你 在 一 个 合 并 请 求 中 有 任 务 清 单, 你 将 会 在 所 有 合 并 请 求 的 总 览 页 面 上 看 到 它 的 进 度 这 使 得 人 们 可 以 把 一 个 合 并 请 求 分 解 成 不 同 的 小 任 务, 同 时 便 于 其 他 人 了 解 分 支 的 进 度 你 可 以 在 在 合 并 请 求 列 表 中 的 任 务 列 表 总 结 看 到 一 个 例 子 Figure 104. 在 合 并 请 求 列 表 中 的 任 务 列 表 总 结 当 你 在 实 现 一 个 任 务 的 早 期 就 提 交 合 并 请 求, 并 使 用 任 务 清 单 追 踪 你 的 进 度, 这 个 功 能 会 十 分 的 有 用

178 摘 录 代 码 你 也 可 以 在 评 论 中 摘 录 代 码 这 在 你 想 要 展 示 尚 未 提 交 到 分 支 中 的 代 码 时 会 十 分 有 用 它 也 经 常 被 用 在 展 示 无 法 正 常 工 作 的 代 码 或 这 个 合 并 请 求 需 要 的 代 码 你 需 要 用 反 引 号 将 需 要 添 加 的 摘 录 代 码 包 起 来 ```java for(int i=0 ; i < 5 ; i++) { System.out.println("i is : " + i); } ``` 如 果 加 入 语 言 的 名 称, 就 像 我 们 这 里 加 入 的 java 一 样,GitHub 会 自 动 尝 试 对 摘 录 的 片 段 进 行 语 法 高 亮 在 下 面 的 例 子 中, 它 最 终 会 渲 染 成 这 个 样 子 : 渲 染 后 的 摘 录 代 码 示 例 Figure 105. 渲 染 后 的 摘 录 代 码 示 例 引 用 如 果 你 在 回 复 一 个 很 长 的 评 论 之 中 的 一 小 段, 你 只 需 要 复 制 你 需 要 的 片 段, 并 在 每 行 前 添 加 > 符 号 即 可 事 实 上, 因 为 这 个 功 能 会 被 经 常 用 到, 它 也 有 一 个 快 捷 键 只 要 你 把 你 要 回 应 的 文 字 选 中, 并 按 下 r 键, 选 中 的 问 题 会 自 动 引 用 并 填 入 评 论 框 引 用 的 部 分 就 像 这 样 : > Whether 'tis Nobler in the mind to suffer > The Slings and Arrows of outrageous Fortune, How big are these slings and in particular, these arrows? 经 过 渲 染 后, 就 会 变 成 这 样 : 渲 染 后 的 引 用 示 例

179 Figure 106. 渲 染 后 的 引 用 示 例 表 情 符 号 (Emoji) 最 后, 我 们 可 以 在 评 论 中 使 用 表 情 符 号 这 经 常 出 现 在 GitHub 的 议 题 和 合 并 请 求 的 评 论 中 GitHub 上 甚 至 有 表 情 助 手 如 果 你 在 输 入 评 论 时 以 : 开 头, 自 动 完 成 器 会 帮 助 你 找 到 你 需 要 的 表 情 Figure 107. 表 情 符 号 自 动 完 成 器 你 也 可 以 在 评 论 的 任 何 地 方 使 用 :< 表 情 名 称 >: 来 添 加 表 情 符 号 举 个 例 子, 你 可 以 输 入 以 下 文 字 :

180 I :eyes: that :bug: and I :cold_sweat:. :trophy: for :microscope: it. :+1: and :sparkles: on this :ship:, it's :fire::poop:! :clap::tada::panda_face: 渲 染 之 后, 就 会 变 成 这 样 : 使 用 了 大 量 表 情 符 号 的 评 论 Figure 108. 使 用 了 大 量 表 情 符 号 的 评 论 虽 然 这 个 功 能 并 不 是 非 常 实 用, 但 是 它 在 这 种 不 方 便 表 达 感 情 的 媒 体 里, 加 入 了 趣 味 的 元 素 NOTE 事 实 上 现 在 已 经 有 大 量 的 在 线 服 务 可 以 使 用 表 情 符 号, 这 里 有 个 列 表 可 以 让 你 快 速 的 找 到 能 表 达 你 的 情 绪 的 表 情 符 号 : 图 片 从 技 术 层 面 来 说, 这 并 不 是 GitHub 风 格 Markdown 的 功 能, 但 是 也 很 有 用 如 果 不 想 使 用 Markdown 语 法 来 插 入 图 片,GitHub 允 许 你 通 过 拖 拽 图 片 到 文 本 区 来 插 入 图 片

181 Figure 109. 通 过 拖 拽 的 方 式 自 动 插 入 图 片 如 果 你 回 去 查 看 在 合 并 请 求 中 的 交 叉 引 用, 你 会 发 现 文 本 区 上 有 个 Parsed as Markdown 的 提 示 点 击 它 你 可 以 了 解 所 有 能 在 GitHub 上 使 用 的 Markdown 功 能 维 护 项 目 现 在 我 们 可 以 很 方 便 地 向 一 个 项 目 贡 献 内 容, 来 看 一 下 另 一 个 方 面 的 内 容 : 创 建 维 护 和 管 理 你 自 己 的 项 目 创 建 新 的 版 本 库 让 我 们 创 建 一 个 版 本 库 来 分 享 我 们 的 项 目 通 过 点 击 面 板 右 侧 的 New repository 按 钮, 或 者 顶 部 工 具 条 你 用 户 名 旁 边 的 + 按 钮 来 开 始 我 们 的 旅 程 参 见 这 是 New repository 下 拉 列 表.

182 Figure 110. 这 是 `Your repositories ' 区 域. Figure 111. 这 是 New repository 下 拉 列 表. 这 会 带 你 到 new repository 表 单 :

183 Figure 112. 这 是 new repository 表 单. 这 里 除 了 一 个 你 必 须 要 填 的 项 目 名, 其 他 字 段 都 是 可 选 的 现 在 只 需 要 点 击 Create Repository 按 钮,Duang!!! 你 就 在 GitHub 上 拥 有 了 一 个 以 <user>/<project_name> 命 名 的 新 仓 库 了 因 为 目 前 暂 无 代 码,GitHub 会 显 示 有 关 创 建 新 版 本 库 或 者 关 联 到 一 个 已 有 的 Git 版 本 库 的 一 些 说 明 我 们 不 会 在 这 里 详 细 说 明 此 项, 如 果 你 需 要 复 习, 去 看 Git 基 础 现 在 你 的 项 目 就 托 管 在 GitHub 上 了, 你 可 以 把 URL 给 任 何 你 想 分 享 的 人 GitHub 上 的 项 目 可 通 过 HTTP 或 SSH 访 问, 格 式 是 :HTTP : SSH : git@github.com:<user>/<project_name> Git 可 以 通 过 以 上 两 种 URL 进 行 抓 取 和 推 送, 但 是 用 户 的 访 问 权 限 又 因 连 接 时 使 用 的 证 书 不 同 而 异 NOTE 通 常 对 于 公 开 项 目 可 以 优 先 分 享 基 于 HTTP 的 URL, 因 为 用 户 克 隆 项 目 不 需 要 有 一 个 GitHub 帐 号 如 果 你 分 享 SSH URL, 用 户 必 须 有 一 个 帐 号 并 且 上 传 SSH 密 钥 才 能 访 问 你 的 项 目 HTTP URL 与 你 贴 到 浏 览 器 里 查 看 项 目 用 的 地 址 是 一 样 的 添 加 合 作 者 如 果 你 想 与 他 人 合 作, 并 想 给 他 们 提 交 的 权 限, 你 需 要 把 他 们 添 加 为 Collaborators 如 果 Ben,Jeff,Louise 都 在 GitHub 上 注 册 了, 你 想 给 他 们 推 送 的 权 限, 你 可 以 将 他 们 添 加 到 你 的 项 目 这 样 做 会 给 他 们 推 送 权 限, 就 是 说 他 们 对 项 目 和 Git 版 本 库 都 有 读 写 的 权 限 点 击 边 栏 底 部 的 Settings 链 接

184 Figure 113. 版 本 库 设 置 链 接. 然 后 从 左 侧 菜 单 中 选 择 Collaborators 然 后, 在 输 入 框 中 填 写 用 户 名, 点 击 Add collaborator. 如 果 你 想 授 权 给 多 个 人, 你 可 以 多 次 重 复 这 个 步 骤 如 果 你 想 收 回 权 限, 点 击 他 们 同 一 行 右 侧 的 X Figure 114. 版 本 库 合 作 者. 管 理 合 并 请 求 现 在 你 有 一 个 包 含 一 些 代 码 的 项 目, 可 能 还 有 几 个 有 推 送 权 限 的 合 作 者, 下 面 来 看 当 你 收 到 合 并 请 求 时 该 做 什 么 合 并 请 求 可 以 来 自 仓 库 副 本 的 一 个 分 支, 或 者 同 一 仓 库 的 另 一 个 分 支 唯 一 的 区 别 是 fork 过 来 的 通 常 是 和 你 不 能 互 相 推 送 的 人, 而 内 部 的 推 送 通 常 都 可 以 互 相 访 问

185 作 为 例 子, 假 设 你 是 tonychacon, 你 创 建 了 一 个 名 为 fade 的 Arduino 项 目. 邮 件 通 知 有 人 来 修 改 了 你 的 代 码, 给 你 发 了 一 个 合 并 请 求 你 会 收 一 封 关 于 合 并 请 求 的 提 醒 邮 件, 它 看 起 来 像 新 的 合 并 请 求 的 邮 件 通 知. Figure 115. 新 的 合 并 请 求 的 邮 件 通 知. 关 于 这 个 邮 件 有 几 个 要 注 意 的 地 方 它 会 给 你 一 个 小 的 变 动 统 计 结 果 一 个 包 含 合 并 请 求 中 改 变 的 文 件 和 改 变 了 多 少 的 列 表 它 还 给 你 一 个 GitHub 上 进 行 合 并 请 求 操 作 的 链 接 还 有 几 个 可 以 在 命 令 行 使 用 的 URL 如 果 你 注 意 到 git pull <url> patch-1 这 一 行, 这 是 一 种 合 并 远 程 分 支 的 简 单 方 式, 无 需 必 须 添 加 一 个 远 程 分 支 我 们 很 快 会 在 检 出 远 程 分 支._ 讲 到 它 如 果 你 愿 意, 你 可 以 创 建 并 切 换 到 一 个 主 题 分 支, 然 后 运 行 这 个 命 令 把 合 并 请 求 合 并 进 来 还 有 一 些 有 趣 的 URL, 像.diff 和.patch, 就 像 你 猜 的 那 样, 它 们 提 供 diff 和 patch 的 标 准 版 本 你 可 以 技 术 性 地 用 下 面 的 方 法 合 并 合 并 请 求 : $ curl git am

186 在 合 并 请 求 上 进 行 合 作 就 像 我 们 在 GitHub 流 程,_ 说 过 的, 现 在 你 可 以 跟 开 启 合 并 请 求 的 人 进 行 会 话 你 既 可 以 对 某 些 代 码 添 加 注 释, 也 可 以 对 整 个 提 交 添 加 注 释 或 对 整 个 合 并 请 求 添 加 注 释, 在 任 何 地 方 都 可 以 用 GitHub Flavored Markdown 每 次 有 人 在 合 并 请 求 上 进 行 注 释 你 都 会 收 到 通 知 邮 件, 通 知 你 哪 里 发 生 改 变 他 们 都 会 包 含 一 个 到 改 变 位 置 的 链 接, 你 可 以 直 接 在 邮 件 中 对 合 并 请 求 进 行 注 释 Figure 116. Responses to s are included in the thread. 一 旦 代 码 符 合 了 你 的 要 求, 你 想 把 它 合 并 进 来, 你 可 以 把 代 码 拉 取 下 来 在 本 地 进 行 合 并, 也 可 以 用 我 们 之 前 提 到 过 的 git pull <url> <branch> 语 法, 或 者 把 fork 添 加 为 一 个 remote, 然 后 进 行 抓 取 和 合 并 对 于 很 琐 碎 的 合 并, 你 也 可 以 用 GitHub 网 站 上 的 Merge 按 钮 它 会 做 一 个 non-fast-forward 合 并, 即 使 可 以 快 进 (fast-forward) 合 并 也 会 产 生 一 个 合 并 提 交 记 录 就 是 说 无 论 如 何, 只 要 你 点 击 merge 按 钮, 就 会 产 生 一 个 合 并 提 交 记 录 你 可 以 在 合 并 按 钮 和 手 工 合 并 一 个 合 并 请 求 的 指 令. 看 到, 如 果 你 点 击 提 示 链 接,GitHub 会 给 你 所 有 的 这 些 信 息

187 Figure 117. 合 并 按 钮 和 手 工 合 并 一 个 合 并 请 求 的 指 令. 如 果 你 决 定 不 合 并 它, 你 可 以 把 合 并 请 求 关 掉, 开 启 合 并 请 求 的 人 会 收 到 通 知 合 并 请 求 引 用 如 果 你 正 在 处 理 许 多 合 并 请 求, 不 想 添 加 一 堆 remote 或 者 每 次 都 要 做 一 次 拉 取, 这 里 有 一 个 可 以 在 GitHub 上 用 的 小 技 巧 这 是 有 点 高 级 的 技 巧, 但 它 相 当 有 用, 我 们 会 在 引 用 规 格 有 更 多 的 细 节 说 明 实 际 上 GitHub 在 服 务 器 上 把 合 并 请 求 分 支 视 为 一 种 假 分 支 默 认 情 况 下 你 克 隆 时 不 会 得 到 它 们, 但 它 们 还 是 隐 式 地 存 在, 你 可 以 很 容 易 地 访 问 到 它 们 为 了 展 示 这 个, 我 们 要 用 到 一 个 叫 做 ls-remote 的 低 级 命 令 ( 通 常 被 叫 做 plumbing, 我 们 会 在 底 层 命 令 和 高 层 命 令 读 到 更 多 相 关 内 容 ) 这 个 命 令 在 日 常 Git 操 作 中 基 本 不 会 用 到, 但 在 显 示 服 务 器 上 有 哪 些 引 用 (reference) 时 很 管 用 如 果 在 我 们 之 前 用 过 的 blink 版 本 库 上 使 用 这 个 命 令, 我 们 会 得 到 一 个 版 本 库 里 所 有 的 分 支, 标 签 和 其 它 引 用 (reference) 的 列 表

188 $ git ls-remote 10d539600d ec636870a504f4fee4d HEAD 10d539600d ec636870a504f4fee4d refs/heads/master 6a83107c62950be9453aac297bb0193fd743cd6e refs/pull/1/head afe83c2d1a70674c9505cc1d8b7d380d5e076ed3 refs/pull/1/merge 3c8d735ee16296c242be7a9742ebfbc2665adec1 refs/pull/2/head 15c9f4f80973a ab2066b6ad9fe8dcf03d refs/pull/2/merge a5a7751a33b7e86c5e9bb07b26001bb17d775d1a refs/pull/4/head 31a45fc257e8433c8d8804e3e848cf61c9d3166c refs/pull/4/merge 当 然, 如 果 你 在 你 自 己 的 版 本 库 或 其 它 你 想 检 查 的 远 程 版 本 库 中 使 用 git ls-remote origin, 它 会 显 示 相 似 的 内 容 如 果 版 本 库 在 GitHub 上 并 且 有 打 开 的 合 并 请 求, 你 会 得 到 一 些 以 refs/pull/ 开 头 的 引 用 它 们 实 际 上 是 分 支, 但 因 为 它 们 不 在 refs/heads/ 中, 所 以 正 常 情 况 下 你 克 隆 时 不 会 从 服 务 器 上 得 到 它 们 抓 取 过 程 正 常 情 况 下 会 忽 略 它 们 每 个 合 并 请 求 有 两 个 引 用 - 其 中 以 /head 结 尾 的 引 用 指 向 的 提 交 记 录 与 合 并 请 求 分 支 中 的 最 后 一 个 提 交 记 录 是 同 一 个 所 以 如 果 有 人 在 我 们 的 版 本 库 中 开 启 了 一 个 合 并 请 求, 他 们 的 分 支 叫 做 bug-fix, 指 向 a5a775 这 个 提 交 记 录, 那 么 在 我 们 的 版 本 库 中 我 们 没 有 bug-fix 分 支 ( 因 为 那 是 在 他 们 的 fork 中 ), 但 我 们 可 以 有 一 个 pull/<pr#>/head 指 向 a5a775 这 意 味 着 我 们 可 以 很 容 易 地 拉 取 每 一 个 合 并 请 求 分 支 而 不 用 添 加 一 堆 remote 现 在, 你 可 以 像 直 接 抓 取 引 用 一 样 抓 取 那 些 分 支 或 提 交 $ git fetch origin refs/pull/958/head From * branch refs/pull/958/head -> FETCH_HEAD 这 告 诉 Git: 连 接 到 origin 这 个 remote, 下 载 名 字 为 refs/pull/958/head 的 引 用 Git 高 高 兴 兴 去 执 行, 下 载 构 建 那 个 引 用 需 要 的 所 有 内 容, 然 后 把 指 针 指 向.git/FETCH_HEAD 下 面 你 想 要 的 提 交 记 录 然 后 你 可 以 用 git merge FETCH_HEAD 把 它 合 并 到 你 想 进 行 测 试 的 分 支, 但 那 个 合 并 的 提 交 信 息 看 起 来 有 点 怪 然 而, 如 果 你 需 要 审 查 一 大 批 合 并 请 求, 这 样 操 作 会 很 麻 烦 还 有 一 种 方 法 可 以 抓 取 所 有 的 合 并 请 求, 并 且 在 你 连 接 到 远 程 (remote) 的 时 候 保 持 更 新 用 你 最 喜 欢 的 编 辑 器 打 开.git/config, 查 找 origin 远 程 (remote) 看 起 来 差 不 多 像 下 面 这 样 : [remote "origin"] url = fetch = +refs/heads/*:refs/remotes/origin/* 以 fetch = 开 头 的 行 是 一 个 refspec. 它 是 一 种 把 remote 的 名 称 映 射 到 你 本 地.git 目 录 的 方 法 这 一 条

189 ( 就 是 上 面 的 这 一 条 ) 告 诉 Git, remote 上 refs/heads 下 面 的 内 容 在 我 本 地 版 本 库 中 都 放 在 refs/remotes/origin 你 可 以 把 这 一 段 修 改 一 下, 添 加 另 一 个 refspec: [remote "origin"] url = fetch = +refs/heads/*:refs/remotes/origin/* fetch = +refs/pull/*/head:refs/remotes/origin/pr/* 最 后 一 行 告 诉 Git: 所 有 看 起 来 像 refs/pull/123/head 的 引 用 应 该 在 本 地 版 本 库 像 refs/remotes/origin/pr/123 一 样 存 储 现 在, 如 果 你 保 存 那 个 文 件, 执 行 git fetch: $ git fetch # * [new ref] refs/pull/1/head -> origin/pr/1 * [new ref] refs/pull/2/head -> origin/pr/2 * [new ref] refs/pull/4/head -> origin/pr/4 # 现 在 所 有 的 合 并 请 求 在 本 地 像 分 支 一 样 展 现, 它 们 是 只 读 的, 当 你 执 行 抓 取 时 它 们 也 会 更 新 这 让 在 本 地 测 试 合 并 请 求 中 的 代 码 变 得 超 级 简 单 : $ git checkout pr/2 Checking out files: 100% (3769/3769), done. Branch pr/2 set up to track remote branch pr/2 from origin. Switched to a new branch 'pr/2' 你 的 鹰 眼 系 统 会 发 现 在 refspec 的 remote 部 分 的 结 尾 有 个 head 在 GitHub 那 边 也 有 一 个 refs/pull/#/merge 引 用, 它 代 表 的 是 如 果 你 在 网 站 上 按 了 merge 按 钮 对 应 的 提 交 记 录 这 甚 至 让 你 可 以 在 按 按 钮 之 前 就 测 试 这 个 合 并 合 并 请 求 之 上 的 合 并 请 求 你 不 仅 可 以 在 主 分 支 或 者 说 master 分 支 上 开 启 合 并 请 求, 实 际 上 你 可 以 在 网 络 上 的 任 何 一 个 分 支 上 开 启 合 并 请 求 其 实, 你 甚 至 可 以 在 另 一 个 合 并 请 求 上 开 启 一 个 合 并 请 求 如 果 你 看 到 一 个 合 并 请 求 在 向 正 确 的 方 向 发 展, 然 后 你 想 在 这 个 合 并 请 求 上 做 一 些 修 改 或 者 你 不 太 确 定 这 是 个 好 主 意, 或 者 你 没 有 目 标 分 支 的 推 送 权 限, 你 可 以 直 接 在 合 并 请 求 上 开 启 一 个 合 并 请 求 当 你 开 启 一 个 合 并 请 求 时, 在 页 面 的 顶 端 有 一 个 框 框 显 示 你 要 合 并 到 哪 个 分 支 和 你 从 哪 个 分 支 合 并 过 来 的 如 果 你 点 击 那 个 框 框 右 边 的 Edit 按 钮, 你 不 仅 可 以 改 变 分 支, 还 可 以 选 择 哪 个 fork

190 Figure 118. 手 工 修 改 合 并 请 求 的 目 标. 这 里 你 可 以 很 简 单 地 指 明 合 并 你 的 分 支 到 哪 一 个 合 并 请 求 或 fork 提 醒 和 通 知 GitHub 内 置 了 一 个 很 好 的 通 知 系 统, 当 你 需 要 与 别 人 或 别 的 团 队 交 流 时 用 起 来 很 方 便 在 任 何 评 论 中 你 可 以 先 输 入 一 个 系 统 会 自 动 补 全 项 目 中 合 作 者 或 贡 献 者 的 名 字 和 用 户 名 Figure 119. 输 来 提 醒 某 人. 你 也 可 以 提 醒 不 在 列 表 中 的 用 户, 但 是 通 常 自 动 补 全 用 起 更 快 当 你 发 布 了 一 个 带 用 户 提 醒 的 评 论, 那 个 用 户 会 收 到 通 知 这 意 味 着 把 人 们 拉 进 会 话 中 要 比 让 他 们 投 票 有 效 率 得 多 对 于 GitHub 上 的 合 并 请 求, 人 们 经 常 把 他 们 团 队 或 公 司 中 的 其 它 人 拉 来 审 查 问 题 或 合 并 请 求 如 果 有 人 收 到 了 合 并 请 求 或 问 题 的 提 醒, 他 们 会 " 订 阅 " 它, 后 面 有 新 的 活 动 发 生 他 们 都 会 持 续 收 到 提 醒 如 果

191 你 是 合 并 请 求 或 者 问 题 的 发 起 方 你 也 会 被 订 阅 上, 比 如 你 在 关 注 一 个 版 本 库 或 者 你 评 论 了 什 么 东 西 如 果 你 不 想 再 收 到 提 醒, 在 页 面 上 有 个 Unsubscribe 按 钮, 点 一 下 就 不 会 再 收 到 更 新 了 Figure 120. 取 消 订 阅 一 个 问 题 或 合 并 请 求. 通 知 页 面 当 我 们 在 这 提 到 特 指 GitHub 的 notifications, 指 的 是 当 GitHub 上 有 事 件 发 生 时, 它 通 知 你 的 方 式, 这 里 有 几 种 不 同 的 方 式 来 配 置 它 们 如 果 你 打 开 配 置 页 面 的 Notification center 标 签, 你 可 以 看 到 一 些 选 项

192 Figure 121. 通 知 中 心 选 项. 有 两 个 选 项, 通 过 " 邮 件 ( )" 和 通 过 " 网 页 (Web)", 你 可 以 选 用 一 个 或 者 都 不 选 或 者 都 选 网 页 通 知 网 页 通 知 只 在 GitHub 上 存 在, 你 也 只 能 在 GitHub 上 查 看 如 果 你 打 开 了 这 个 选 项 并 且 有 一 个 你 的 通 知, 你 会 在 你 屏 幕 上 方 的 通 知 图 标 上 看 到 一 个 小 蓝 点 参 见 通 知 中 心. Figure 122. 通 知 中 心. 如 果 你 点 击 那 个 玩 意 儿, 你 会 看 到 你 被 通 知 到 的 所 有 条 目, 按 照 项 目 分 好 了 组 你 可 以 点 击 左 边 栏 的 项 目 名 字 来

193 过 滤 项 目 相 关 的 通 知 你 可 以 点 击 通 知 旁 边 的 对 号 图 标 把 通 知 标 为 已 读, 或 者 点 击 组 上 面 的 图 标 把 项 目 中 所 有 的 通 知 标 为 已 读 在 每 个 对 号 图 标 旁 边 都 有 一 个 静 音 按 钮, 你 可 以 点 一 下, 以 后 就 不 会 收 到 它 相 关 的 通 知 所 有 这 些 工 具 对 于 处 理 大 量 通 知 非 常 有 用 很 多 GitHub 资 深 用 户 都 关 闭 邮 件 通 知, 在 这 个 页 面 上 处 理 他 们 所 有 的 通 知 邮 件 通 知 邮 件 通 知 是 你 处 理 GitHub 通 知 的 另 一 种 方 式 如 果 你 打 开 这 个 选 项, 每 当 有 通 知 时, 你 会 收 到 一 封 邮 件 我 们 在 通 过 电 子 邮 件 发 送 的 评 论 提 醒 和 新 的 合 并 请 求 的 邮 件 通 知. 看 到 了 一 些 例 子 邮 件 也 会 被 合 适 地 按 话 题 组 织 在 一 起, 如 果 你 使 用 一 个 具 有 会 话 功 能 的 邮 件 客 户 端 那 会 很 方 便 GitHub 在 发 送 给 你 的 邮 件 头 中 附 带 了 很 多 元 数 据, 这 对 于 设 置 过 滤 器 和 邮 件 规 则 非 常 有 帮 助 举 个 例 子, 我 们 来 看 一 看 在 新 的 合 并 请 求 的 邮 件 通 知. 中 发 给 Tony 的 一 封 真 实 邮 件 的 头 部, 我 们 会 看 到 下 面 这 些 : To: tonychacon/fade <fade@noreply.github.com> Message-ID: <tonychacon/fade/pull/1@github.com> Subject: [fade] Wait longer to see the dimming effect better (#1) X-GitHub-Recipient: tonychacon List-ID: tonychacon/fade <fade.tonychacon.github.com> List-Archive: List-Post: <mailto:reply+i-4xxx@reply.github.com> List-Unsubscribe: <mailto:unsub+i-xxx@reply.github.com>,... X-GitHub-Recipient-Address: tchacon@example.com 这 里 有 一 些 有 趣 的 东 西 如 果 你 想 高 亮 或 者 转 发 这 个 项 目 甚 至 这 个 合 并 请 求 相 关 的 邮 件,Message-ID 中 的 信 息 会 以 <user>/<project>/<type>/<id> 的 格 式 展 现 所 有 的 数 据 例 如, 如 果 这 是 一 个 问 题 (issue), 那 么 <type> 字 段 就 会 是 issues 而 不 是 pull List-Post 和 List-Unsubscribe 字 段 表 示 如 果 你 的 邮 件 客 户 端 能 够 处 理 这 些, 那 么 你 可 以 很 容 易 地 在 列 表 中 发 贴 或 取 消 对 这 个 相 关 帖 子 的 订 阅 那 会 很 有 效 率, 就 像 在 页 面 中 点 击 静 音 按 钮 或 在 问 题 / 合 并 请 求 页 面 点 击 Unsubscribe 一 样 值 得 注 意 的 是, 如 果 你 同 时 打 开 了 邮 件 和 网 页 通 知, 那 么 当 你 在 邮 件 客 户 端 允 许 加 载 图 片 的 情 况 下 阅 读 邮 件 通 知 时, 对 应 的 网 页 通 知 也 将 会 同 时 被 标 记 为 已 读 特 殊 文 件 如 果 你 的 版 本 库 中 有 一 些 特 殊 文 件,GitHub 会 提 醒 你

194 README 第 一 个 就 是 README 文 件, 可 以 是 几 乎 任 何 GitHub 可 以 识 别 的 格 式 例 如, 它 可 以 是 README,README.md, README.asciidoc 如 果 GitHub 在 你 的 版 本 库 中 找 到 README 文 件, 会 把 它 在 项 目 的 首 页 渲 染 出 来 很 多 团 队 在 这 个 文 件 里 放 版 本 库 或 项 目 新 人 需 要 了 解 的 所 有 相 关 的 信 息 它 一 般 包 含 这 些 内 容 : 该 项 目 的 作 用 如 何 配 置 与 安 装 有 关 如 何 使 用 和 运 行 的 例 子 项 目 的 许 可 证 如 何 向 项 目 贡 献 力 量 因 为 GitHub 会 渲 染 这 个 文 件, 你 可 以 在 文 件 里 植 入 图 片 或 链 接 让 它 更 容 易 理 解 贡 献 CONTRIBUTING 另 一 个 GitHub 可 以 识 别 的 特 殊 文 件 是 CONTRIBUTING 如 果 你 有 一 个 任 意 扩 展 名 的 CONTRIBUTING 文 件, 当 有 人 开 启 一 个 合 并 请 求 时 GitHub 会 显 示 开 启 合 并 请 求 时 有 CONTRIBUTING 文 件 存 在. Figure 123. 开 启 合 并 请 求 时 有 CONTRIBUTING 文 件 存 在. 这 个 的 作 用 就 是 你 可 以 在 这 里 指 出 对 于 你 的 项 目 开 启 的 合 并 请 求 你 想 要 的 / 不 想 要 的 各 种 事 情 这 样 别 人 在 开 启 合 并 请 求 之 前 可 以 读 到 这 些 指 导 方 针 项 目 管 理 对 于 一 个 单 个 项 目 其 实 没 有 很 多 管 理 事 务 要 做, 但 也 有 几 点 有 趣 的

195 改 变 默 认 分 支 如 果 你 想 用 master 之 外 的 分 支 作 为 你 的 默 认 分 支, 其 他 人 将 默 认 会 在 这 个 分 支 上 开 启 合 并 请 求 或 进 行 浏 览, 你 可 以 在 你 版 本 库 的 设 置 页 面 的 "options" 标 签 下 修 改 Figure 124. 改 变 项 目 的 默 认 分 支. 简 单 地 改 变 默 认 分 支 下 拉 列 表 中 的 选 项, 它 就 会 作 为 所 有 主 要 操 作 的 默 认 分 支, 他 人 进 行 克 隆 时 该 分 支 也 将 被 默 认 检 出 移 交 项 目 如 果 你 想 把 一 个 项 目 移 交 给 GitHub 中 的 另 一 个 人 或 另 一 个 组 织, 还 是 设 置 页 面 的 这 个 "options" 标 签 下 有 一 个 Transfer ownership 选 项 可 以 用 来 干 这 个 Figure 125. 把 项 目 移 交 给 另 一 个 GitHub 用 户 或 组 织 当 你 正 准 备 放 弃 一 个 项 目 且 正 好 有 别 人 想 要 接 手 时, 或 者 你 的 项 目 壮 大 了 想 把 它 移 到 一 个 组 织 里 时, 这 就 管 用 了 这 么 做 不 仅 会 把 版 本 库 连 带 它 所 有 的 观 察 和 星 标 数 都 移 到 另 一 个 地 方, 它 还 会 将 你 的 URL 重 定 向 到 新 的 位 置 它 也 重 定 向 了 来 自 Git 的 克 隆 和 抓 取, 而 不 仅 仅 是 网 页 端 请 求

196 管 理 组 织 除 了 个 人 帐 户 之 外,GitHub 还 提 供 被 称 为 组 织 (Organizations) 的 帐 户 组 织 账 户 和 个 人 账 户 一 样 都 有 一 个 用 于 存 放 所 拥 有 项 目 的 命 名 空 间, 但 是 许 多 其 他 的 东 西 都 是 不 同 的 组 织 帐 户 代 表 了 一 组 共 同 拥 有 多 个 项 目 的 人, 同 时 也 提 供 一 些 工 具 用 于 对 成 员 进 行 分 组 管 理 通 常, 这 种 账 户 被 用 于 开 源 群 组 ( 例 如 : perl 或 者 rails ), 或 者 公 司 ( 例 如 : google 或 者 twitter ) 组 织 的 基 本 知 识 我 们 可 以 很 简 单 地 创 建 一 个 组 织, 只 需 要 点 击 任 意 GitHub 页 面 右 上 角 的 + 图 标, 在 菜 单 中 选 择 New organization 即 可 Figure 126. `New organization ' 菜 单 项 首 先 你 必 须 提 供 组 织 的 名 称 和 组 织 的 主 要 联 系 邮 箱 然 后, 如 果 你 希 望 的 话, 也 可 以 邀 请 其 他 用 户 作 为 共 同 拥 有 人 完 成 以 上 步 骤 后, 你 就 会 拥 有 一 个 全 新 的 组 织 类 似 于 个 人 帐 户, 如 果 组 织 的 所 有 内 容 都 是 开 源 的, 那 么 你 就 可 以 免 费 使 用 这 个 组 织 作 为 一 个 组 织 的 拥 有 者, 当 你 在 派 生 一 个 版 本 库 的 时 候, 你 可 以 选 择 把 它 派 生 到 你 的 组 织 的 命 名 空 间 内 当 你 新 建 版 本 库 时, 你 可 以 把 它 存 放 到 你 的 个 人 帐 户 或 你 拥 有 的 组 织 内 同 时, 你 也 会 自 动 地 关 注 所 有 这 些 组 织 内 的 新 版 本 库 就 像 头 像, 你 可 以 为 你 的 组 织 上 传 头 像, 使 它 更 个 性 化 同 时, 也 和 个 人 帐 户 类 似, 组 织 会 有 一 个 着 陆 页 (landing page), 用 于 列 出 该 组 织 所 有 的 版 本 库, 并 且 该 页 面 可 供 所 有 人 浏 览 下 面 我 们 来 说 一 些 组 织 和 个 人 帐 户 不 同 的 地 方 团 队 组 织 使 用 团 队 (Teams) 来 管 理 成 员, 团 队 就 是 组 织 中 的 一 组 个 人 账 户 和 版 本 库, 以 及 团 队 成 员 对 这 些 版 本 库 的 访 问 权 限 例 如, 假 设 你 的 公 司 有 三 个 版 本 库 :frontend backend 和 deployscripts 你 会 希 望 你 的

197 HTML/CSS/Javascript 开 发 者 有 frontend 或 者 backend 的 访 问 权 限, 操 作 人 员 有 backend 和 deployscripts 的 访 问 权 限 团 队 让 这 个 任 务 变 得 更 简 单, 而 不 用 为 每 个 版 本 库 管 理 它 的 协 作 者 组 织 页 面 主 要 由 一 个 面 板 (dashboard) 构 成, 这 个 仪 表 盘 包 含 了 这 个 组 织 内 的 所 有 版 本 库, 用 户 和 团 队 Figure 127. 组 织 页 面 你 可 以 点 击 组 织 页 面 右 边 的 团 队 侧 边 栏 (Teams) 来 管 理 你 的 团 队 点 击 之 后, 你 会 进 入 一 个 新 页 面, 在 这 里 你 可 以 添 加 新 成 员 和 版 本 库 到 团 队 中, 或 者 管 理 团 队 的 访 问 权 限 和 其 它 设 置 每 个 团 队 对 于 版 本 库 可 以 有 只 读 读 写 和 管 理 三 种 权 限 你 可 以 通 过 点 击 在 团 队 页 面 内 的 Settings 按 钮 更 改 相 应 权 限 等 级

198 Figure 128. 团 队 页 面 当 你 邀 请 一 个 用 户 加 入 团 队, 该 用 户 会 收 到 一 封 通 知 他 被 邀 请 的 邮 件 除 此 之 外, 团 队 也 类 似 于 个 人 帐 户, 例 如 的 功 能, 不 同 之 处 就 在 于 被 提 及 的 团 队 内 * 所 有 * 成 员 都 会 成 为 这 个 话 题 的 订 阅 者 当 你 希 望 得 到 团 队 中 某 个 人 的 关 注, 又 不 知 道 具 体 应 该 问 谁 的 时 候, 这 个 功 能 就 显 得 很 有 帮 助 一 个 用 户 可 以 加 入 任 意 数 量 的 团 队, 所 以 别 把 自 己 局 限 于 拥 有 访 问 控 制 的 团 队 对 于 某 一 类 课 题, 像 ux, css 或 者 refactoring 这 样 有 着 特 殊 关 注 点 的 团 队 就 显 得 很 有 帮 助, 而 像 legal 和 colorblind 这 样 的 就 完 全 是 针 对 它 们 各 自 领 域 的 审 计 日 志 组 织 的 拥 有 者 还 可 以 访 问 组 织 中 发 生 的 事 情 的 所 有 信 息 在 Audit Log 标 签 页 有 整 个 组 织 的 日 志, 你 可 以 看 到 谁 在 世 界 上 哪 个 地 方 做 了 什 么 事

199 Figure 129. 审 计 日 志 你 也 可 以 通 过 选 定 某 一 类 型 的 事 件 某 个 地 方 某 个 人 对 日 志 进 行 过 滤 脚 本 GitHub 所 以 现 在 我 们 已 经 介 绍 了 GitHub 的 大 部 分 功 能 与 工 作 流 程, 但 是 任 意 一 个 小 组 或 项 目 都 会 去 自 定 义, 因 为 他 们 想 要 创 造 或 扩 展 想 要 整 合 的 服 务 对 我 们 来 说 很 幸 运 的 是,GitHub 在 许 多 方 面 都 真 的 很 方 便 Hack 在 本 节 中 我 们 将 会 介 绍 如 何 使 用 GitHub 钩 子 系 统 与 API 接 口, 使 GitHub 按 照 我 们 的 设 想 来 工 作

200 钩 子 GitHub 仓 库 管 理 中 的 钩 子 与 服 务 区 块 是 GitHub 与 外 部 系 统 交 互 最 简 单 的 方 式 服 务 首 先 我 们 来 看 一 下 服 务 钩 子 与 服 务 整 合 都 可 以 在 仓 库 的 设 置 区 块 中 找 到, 就 在 我 们 之 前 添 加 协 作 者 与 改 变 项 目 的 默 认 分 支 的 地 方 在 Webhooks and Services 标 签 下 你 会 看 到 与 服 务 与 钩 子 配 置 区 域 类 似 的 内 容 Figure 130. 服 务 与 钩 子 配 置 区 域 有 许 多 可 以 选 择 的 服 务, 大 多 数 是 整 合 到 其 他 的 商 业 与 开 源 系 统 中 它 们 中 的 大 多 数 是 为 了 整 合 持 续 集 成 服 务 BUG 与 问 题 追 踪 系 统 聊 天 室 系 统 与 文 档 系 统 我 们 将 会 通 过 设 置 一 个 非 常 简 单 的 例 子 来 介 绍 如 果 从 Add Service 选 择 , 会 得 到 一 个 类 似 电 子 邮 件 服 务 配 置 的 配 置 屏 幕

201 Figure 131. 电 子 邮 件 服 务 配 置 在 本 例 中, 如 果 我 们 点 击 Add service 按 钮, 每 次 有 人 推 送 内 容 到 仓 库 时, 指 定 的 电 子 邮 件 地 址 都 会 收 到 一 封 邮 件 服 务 可 以 监 听 许 多 不 同 类 型 的 事 件, 但 是 大 多 数 只 监 听 推 送 事 件 然 后 使 用 那 些 数 据 做 一 些 事 情 如 果 有 一 个 正 在 使 用 的 系 统 想 要 整 合 到 GitHub, 应 当 先 检 查 这 里 看 有 没 有 已 有 的 可 用 的 服 务 整 合 例 如, 如 果 正 使 用 Jenkins 来 测 试 你 的 代 码 库, 当 每 次 有 人 推 送 到 你 的 仓 库 时 你 可 以 启 用 Jenkins 内 置 的 整 合 启 动 测 试 运 行 钩 子 如 果 需 要 做 一 些 更 具 体 的 事, 或 者 想 要 整 合 一 个 不 在 这 个 列 表 中 的 服 务 或 站 点, 可 以 转 而 使 用 更 通 用 的 钩 子 系 统 GitHub 仓 库 钩 子 是 非 常 简 单 的 指 定 一 个 URL 然 后 GitHub 在 任 一 期 望 的 事 件 发 生 时 就 会 发 送 一 个 HTTP 请 求 到 那 个 URL 通 常 做 这 件 事 的 方 式 是 可 以 设 置 一 个 小 的 web 服 务 来 监 听 GitHub 钩 子 请 求 然 后 使 用 收 到 的 数 据 做 一 些 事 情 为 了 启 用 一 个 钩 子, 点 击 服 务 与 钩 子 配 置 区 域 中 的 Add webhook 按 钮 这 会 将 你 引 导 至 一 个 类 似 Web 钩 子 配 置 的 页 面

202 Figure 132. Web 钩 子 配 置 Web 钩 子 的 设 置 非 常 简 单 大 多 数 情 况 下 只 需 要 输 入 一 个 URL 与 一 个 密 钥 然 后 点 击 Add webhook 有 几 个 选 项 可 以 指 定 在 哪 个 事 件 时 想 要 GitHub 发 送 请 求 默 认 的 行 为 是 只 有 当 某 人 推 送 新 代 码 到 仓 库 的 任 一 分 支 时 的 push 事 件 获 得 一 个 请 求 让 我 们 看 一 个 设 置 处 理 web 钩 子 的 web 服 务 的 小 例 子 我 们 将 会 使 用 Ruby web 框 架 Sinatra, 因 为 它 相 当 简 洁, 应 该 能 够 轻 松 地 看 到 我 们 正 在 做 什 么 假 设 我 们 想 要 在 某 个 特 定 的 人 推 送 到 我 们 的 项 目 的 特 定 分 支 并 修 改 一 个 特 定 文 件 时 得 到 一 封 邮 件 我 们 可 以 相 当 容 易 地 使 用 类 似 下 面 的 代 码 做 到 :

203 require 'sinatra' require 'json' require 'mail' post '/payload' do push = JSON.parse(request.body.read) # parse the JSON # gather the data we're looking for pusher = push["pusher"]["name"] branch = push["ref"] # get a list of all the files touched files = push["commits"].map do commit commit['added'] + commit['modified'] + commit['removed'] end files = files.flatten.uniq # check for our criteria if pusher == 'schacon' && branch == 'ref/heads/special-branch' && files.include?('special-file.txt') Mail.deliver do from 'tchacon@example.com' to 'tchacon@example.com' subject 'Scott Changed the File' body "ALARM" end end end 这 里 我 们 拿 到 一 个 GitHub 传 送 给 我 们 的 JSON 请 求 然 后 查 找 推 送 者, 他 们 推 送 到 了 什 么 分 支 以 及 推 送 的 所 有 提 交 都 改 动 了 哪 些 文 件 然 后 我 们 检 查 它 是 否 与 我 们 的 条 件 区 配, 如 果 匹 配 则 发 送 一 封 邮 件 为 了 开 发 与 测 试 类 似 这 样 的 东 西, 在 设 置 钩 子 的 地 方 有 一 个 漂 亮 的 开 发 者 控 制 台 可 以 看 到 GitHub 为 那 个 webhook 的 最 后 几 次 请 求 对 每 一 个 钩 子, 当 它 发 送 后 都 可 以 深 入 挖 掘, 检 测 它 是 否 是 成 功 的 与 请 求 及 回 应 的 消 息 头 与 消 息 体 这 使 得 测 试 与 调 试 钩 子 非 常 容 易

204 Figure 133. Web 钩 子 调 试 信 息 开 发 者 控 制 台 的 另 一 个 很 棒 的 功 能 是 可 以 轻 松 地 重 新 发 送 任 何 请 求 来 测 试 你 的 服 务 关 于 如 何 编 写 web 钩 子 与 所 有 可 监 听 的 不 同 事 件 类 型 的 更 多 信 息, 请 访 问 在 的 GitHub 开 发 者 文 档 GitHub API 服 务 与 钩 子 给 你 提 供 了 一 种 方 式 来 接 收 关 于 在 仓 库 中 发 生 的 事 件 的 推 送 通 知, 但 是 如 何 获 取 相 关 事 件 的 详 情 呢?

205 如 何 自 动 化 一 些 诸 如 添 加 协 作 者 或 给 问 题 加 标 签 的 事 情 呢? 这 是 GitHub API 派 上 用 场 的 地 方 在 自 动 化 流 行 的 趋 势 下,GitHub 提 供 了 大 量 的 API 接 口, 可 以 进 行 几 乎 任 何 能 在 网 站 上 进 行 的 操 作 在 本 节 中 我 们 将 会 学 习 如 何 授 权 与 连 接 到 API, 如 何 通 过 API 在 一 个 问 题 上 评 论 与 如 何 修 改 一 个 Pull Request 的 状 态 基 本 用 途 可 以 做 的 最 基 本 的 事 情 是 向 一 个 不 需 要 授 权 的 接 口 上 发 送 一 个 简 单 的 GET 请 求 该 接 口 可 能 是 一 个 用 户 或 开 源 项 目 的 只 读 信 息 例 如, 如 果 我 们 想 要 知 道 更 多 关 于 名 为 schacon 的 用 户 信 息, 我 们 可 以 运 行 类 似 下 面 的 东 西 : $ curl { "login": "schacon", "id": 70, "avatar_url": " # "name": "Scott Chacon", "company": "GitHub", "following": 19, "created_at": " T17:19:28Z", "updated_at": " T02:37:23Z" } 有 大 量 类 似 这 样 的 接 口 来 获 得 关 于 组 织 项 目 问 题 提 交 的 信 息 差 不 多 就 是 你 能 在 GitHub 上 看 到 的 所 有 东 西 甚 至 可 以 使 用 API 来 渲 染 任 意 Markdown 或 寻 找 一 个.gitignore 模 板

206 $ curl { "name": "Java", "source": "*.class # Mobile Tools for Java (J2ME).mtj.tmp/ # Package Files # *.jar *.war *.ear # virtual machine crash logs, see hs_err_pid* " } 在 一 个 问 题 上 评 论 然 而, 如 果 想 要 在 网 站 上 进 行 一 个 操 作, 如 在 Issue 或 Pull Request 上 评 论, 或 者 想 要 查 看 私 有 内 容 或 与 其 交 互, 你 需 要 授 权 这 里 提 供 了 几 种 授 权 方 式 你 可 以 使 用 仅 需 用 户 名 与 密 码 的 基 本 授 权, 但 是 通 常 更 好 的 主 意 是 使 用 一 个 个 人 访 问 令 牌 可 以 从 设 置 页 的 Applications 标 签 生 成 访 问 令 牌

207 Figure 134. 从 设 置 页 的 Applications 标 签 生 成 访 问 令 牌 它 会 询 问 这 个 令 牌 的 作 用 域 与 一 个 描 述 确 保 使 用 一 个 好 的 描 述 信 息, 这 样 当 脚 本 或 应 用 不 再 使 用 时 你 会 很 放 心 地 移 除 GitHub 只 会 显 示 令 牌 一 次, 所 以 记 得 一 定 要 拷 贝 它 现 在 可 以 在 脚 本 中 使 用 它 代 替 使 用 用 户 名 写 密 码 来 授 权 这 很 漂 亮, 因 为 可 以 限 制 想 要 做 的 范 围 并 且 令 牌 是 可 废 除 的 这 也 会 有 一 个 提 高 频 率 上 限 的 附 加 优 点 如 果 没 有 授 权 的 话, 你 会 被 限 制 在 一 小 时 最 多 发 起 60 次 请 求 如 果 授 权 则 可 以 一 小 时 最 多 发 起 5000 次 请 求 所 以 让 我 们 利 用 它 来 对 我 们 的 其 中 一 个 问 题 进 行 评 论 想 要 对 一 个 特 定 问 题 Issue #6 留 下 一 条 评 论 必 须 使 用 刚 刚 生 成 的 令 牌 作 为 Authorization 头 信 息, 发 送 一 个 到 repos/<user>/<repo>/issues/<num>/comments 的 HTTP POST 请 求

208 $ curl -H "Content-Type: application/json" \ -H "Authorization: token TOKEN" \ --data '{"body":"a new comment, :+1:"}' \ { "id": , "html_url": " ",... "user": { "login": "tonychacon", "id": , "avatar_url": " "type": "User", }, "created_at": " T07:48:19Z", "updated_at": " T07:48:19Z", "body": "A new comment, :+1:" } 现 在 如 果 进 入 到 那 个 问 题, 可 以 看 到 我 们 刚 刚 发 布 的 评 论, 像 从 GitHub API 发 布 的 一 条 评 论 一 样 Figure 135. 从 GitHub API 发 布 的 一 条 评 论 可 以 使 用 API 去 做 任 何 可 以 在 网 站 上 做 的 事 情 创 建 与 设 置 里 程 碑 指 派 人 员 到 Issues 与 Pull Requests, 创 建 与 修 改 标 签 访 问 提 交 数 据 创 建 新 的 提 交 与 分 支 打 开 关 闭 或 合 并 Pull Requests 创 建 与 编 辑 团 队 在 Pull Request 中 评 论 某 行 代 码 搜 索 网 站 等 等 修 改 Pull Request 的 状 态 如 果 使 用 Pull Requests 的 话 我 们 将 要 看 到 的 最 后 一 个 例 子 会 很 有 用 每 一 个 提 交 可 以 有 一 个 或 多 个 与 它 关 联 的 状 态, 有 API 来 添 加 与 查 询 状 态 大 多 数 持 续 集 成 与 测 试 服 务 通 过 测 试 推 送 的 代 码 后 使 用 这 个 API 来 回 应, 然 后 报 告 提 交 是 否 通 过 了 全 部 测 试 你 也 可 以 使 用 该 接 口 来 检 查 提 交 信 息 是 否 经 过 合 适 的 格 式 化 提 交 者 是 否 遵 循 了 所 有 你 的 贡 献 准 则 提 交 是 否 经 过 有 效 的 签 名 种 种 这 类 事 情 假 设 在 仓 库 中 设 置 了 一 个 web 钩 子 访 问 一 个 用 来 检 查 提 交 信 息 中 的 Signed-off-by 字 符 串 的 小 的 web 服 务

209 require 'httparty' require 'sinatra' require 'json' post '/payload' do push = JSON.parse(request.body.read) # parse the JSON repo_name = push['repository']['full_name'] # look through each commit message push["commits"].each do commit # look for a Signed-off-by string if /Signed-off-by/.match commit['message'] state = 'success' description = 'Successfully signed off!' else state = 'failure' description = 'No signoff found.' end # post status to GitHub sha = commit["id"] status_url = " status = { "state" => state, "description" => description, "target_url" => " "context" => "validate/signoff" } HTTParty.post(status_url, :body => status.to_json, :headers => { 'Content-Type' => 'application/json', 'User-Agent' => 'tonychacon/signoff', 'Authorization' => "token #{ENV['TOKEN']}" } ) end end 希 望 这 相 当 容 易 做 在 这 个 web 钩 子 处 理 器 中 我 们 浏 览 刚 刚 推 送 上 来 的 每 一 个 提 交, 在 提 交 信 息 中 查 找 字 符 串 Signed-off-by 并 且 最 终 使 用 HTTP 向 /repos/<user>/<repo>/statuses/<commit_sha> API 接 口 发 送 一 个 带 有 状 态 的 POST 请 求 在 本 例 中 可 以 发 送 一 个 状 态 (success, failure, error) 一 个 发 生 了 什 么 的 描 述 信 息 一 个 用 户 可 以 了 解 更 多 信 息 的 目 标 URL 与 一 个 context 以 防 一 个 单 独 的 提 交 有 多 个 状 态 例 如, 一 个 测 试 服 务 可 以 提 供 一 个 状 态 与

210 一 个 类 似 这 样 的 验 证 服 务 也 可 能 提 供 一 个 状 态 context 字 段 是 用 来 区 别 它 们 的 如 果 某 人 在 GitHub 中 打 开 了 一 个 新 的 Pull Request 并 且 这 个 钩 子 已 经 设 置, 会 看 到 类 似 通 过 API 的 提 交 状 态 的 信 息 Figure 136. 通 过 API 的 提 交 状 态 现 在 可 以 看 到 一 个 小 的 绿 色 对 勾 标 记 在 提 交 信 息 中 有 Signed-off-by 的 提 交 旁 边, 红 色 的 对 勾 标 记 在 作 者 忘 记 签 名 的 提 交 旁 边 也 可 以 看 到 Pull Request 显 示 在 那 个 分 支 上 的 最 后 提 交 的 状 态, 如 果 失 败 的 话 会 警 告 你 如 果 对 测 试 结 果 使 用 这 个 API 那 么 就 不 会 不 小 心 合 并 某 些 未 通 过 测 试 的 最 新 提 交 Octokit 尽 管 我 们 在 这 些 例 子 中 都 是 通 过 curl 与 基 本 的 HTTP 请 求 来 做 几 乎 所 有 的 事 情, 还 有 一 些 以 更 自 然 的 方 式 利 用 API 的 开 源 库 存 在 着 在 写 这 篇 文 章 的 时 候, 被 支 持 的 语 言 包 括 Go Objective-C Ruby 与.NET 访 问 了 解 更 多 相 关 信 息, 它 们 帮 你 处 理 了 更 多 HTTP 相 关 的 内 容 希 望 这 些 工 具 能 帮 助 你 自 定 义 与 修 改 GitHub 来 更 好 地 为 特 定 的 工 作 流 程 工 作 关 于 全 部 API 的 完 整 文 档 与 常 见 任 务 的 指 南, 请 查 阅 总 结 现 在 你 已 经 是 一 名 GitHub 用 户 了 你 知 道 了 如 何 创 建 账 户 管 理 组 织 创 建 和 推 送 版 本 库 向 别 人 的 项 目 提 供 贡 献 以 及 接 受 别 人 的 贡 献 在 下 一 章 中, 你 将 学 习 更 多 强 有 力 的 工 具, 以 及 处 理 复 杂 情 况 的 知 识, 这 些 将 使 你 成 为 真 正 的 Git 大 师

211 Git 工 具 现 在, 你 已 经 学 习 了 管 理 或 者 维 护 Git 仓 库 实 现 代 码 控 制 所 需 的 大 多 数 日 常 命 令 和 工 作 流 程 你 已 经 尝 试 了 跟 踪 和 提 交 文 件 的 基 本 操 作, 并 且 发 挥 了 暂 存 区 和 轻 量 级 的 分 支 及 合 并 的 威 力 接 下 来 你 将 学 习 一 些 Git 的 强 大 功 能, 这 些 功 能 你 可 能 并 不 会 在 日 常 操 作 中 使 用, 但 在 某 些 时 候 你 可 能 会 需 要 选 择 修 订 版 本 Git 允 许 你 通 过 几 种 方 法 来 指 明 特 定 的 或 者 一 定 范 围 内 的 提 交 了 解 它 们 并 不 是 必 需 的, 但 是 了 解 一 下 总 没 坏 处 单 个 修 订 版 本 你 可 以 通 过 Git 给 出 的 SHA-1 值 来 获 取 一 次 提 交, 不 过 还 有 很 多 更 人 性 化 的 方 式 来 做 同 样 的 事 情 本 节 将 会 介 绍 获 取 单 个 提 交 的 多 种 方 法 简 短 的 SHA-1 Git 十 分 智 能, 你 只 需 要 提 供 SHA-1 的 前 几 个 字 符 就 可 以 获 得 对 应 的 那 次 提 交, 当 然 你 提 供 的 SHA-1 字 符 数 量 不 得 少 于 4 个, 并 且 没 有 歧 义 也 就 是 说, 当 前 仓 库 中 只 有 一 个 对 象 以 这 段 SHA-1 开 头 例 如 查 看 一 次 指 定 的 提 交, 假 设 你 执 行 git log 命 令 来 查 看 之 前 新 增 一 个 功 能 的 那 次 提 交 : $ git log commit bc047d87bf7eac ae793478c50d3 Author: Scott Chacon <schacon@gmail.com> Date: Fri Jan 2 18:32: fixed refs handling, added gc auto, updated tests commit d921970aadf03b3cf0e71becdaab3147ba71cdef Merge: 1c002dd... 35cfb2b... Author: Scott Chacon <schacon@gmail.com> Date: Thu Dec 11 15:08: Merge commit 'phedders/rdocs' commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b Author: Scott Chacon <schacon@gmail.com> Date: Thu Dec 11 14:58: added some blame and merge stuff 假 设 这 个 提 交 是 1c002dd..., 如 果 你 想 git show 这 个 提 交, 下 面 的 命 令 是 等 价 的 ( 假 设 简 短 的 版 本 没 有 歧

212 义 ): $ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b $ git show 1c002dd4b536e7479f $ git show 1c002d Git 可 以 为 SHA-1 值 生 成 出 简 短 且 唯 一 的 缩 写 如 果 你 在 git log 后 加 上 --abbrev-commit 参 数, 输 出 结 果 里 就 会 显 示 简 短 且 唯 一 的 值 ; 默 认 使 用 七 个 字 符, 不 过 有 时 为 了 避 免 SHA-1 的 歧 义, 会 增 加 字 符 数 : $ git log --abbrev-commit --pretty=oneline ca82a6d changed the version number 085bb3b removed unnecessary test code a11bef0 first commit 通 常 8 到 10 个 字 符 就 已 经 足 够 在 一 个 项 目 中 避 免 SHA-1 的 歧 义 比 如 Linux 内 核 这 个 相 当 大 的 Git 项 目, 目 前 有 超 过 45 万 个 提 交, 包 含 360 万 个 对 象, 也 只 需 要 前 11 个 字 符 就 能 保 证 唯 一 性 关 于 SHA-1 的 简 短 说 明 许 多 人 觉 得 他 们 的 仓 库 里 有 可 能 出 现 两 个 SHA-1 值 相 同 的 对 象 然 后 呢? 如 果 你 真 的 向 仓 库 里 提 交 了 一 个 跟 之 前 的 某 个 对 象 具 有 相 同 SHA-1 值 的 对 象,Git 发 现 仓 库 里 已 经 存 在 了 拥 有 相 同 HASH 值 的 对 象, 就 会 认 为 这 个 新 的 提 交 是 已 经 被 写 入 仓 库 的 如 果 之 后 你 想 检 出 那 个 对 象 时, 你 将 得 到 先 前 那 个 对 象 的 数 据 NOTE 但 是 这 种 情 况 发 生 的 概 率 十 分 渺 小 SHA-1 摘 要 长 度 是 20 字 节, 也 就 是 160 位 2^80 个 随 机 哈 希 对 象 才 有 50% 的 概 率 出 现 一 次 冲 突 ( 计 算 冲 突 机 率 的 公 式 是 p = (n(n-1)/2) * (1/2^160)) ) 2^80 是 1.2 x 10^24 也 就 是 一 亿 亿 亿 那 是 地 球 上 沙 粒 总 数 的 1200 倍 举 例 说 一 下 怎 样 才 能 产 生 一 次 SHA-1 冲 突 如 果 地 球 上 65 亿 个 人 类 都 在 编 程, 每 人 每 秒 都 在 产 生 等 价 于 整 个 Linux 内 核 历 史 (360 万 个 Git 对 象 ) 的 代 码, 并 将 之 提 交 到 一 个 巨 大 的 Git 仓 库 里 面, 这 样 持 续 两 年 的 时 间 才 会 产 生 足 够 的 对 象, 使 其 拥 有 50% 的 概 率 产 生 一 次 SHA-1 对 象 冲 突 这 要 比 你 编 程 团 队 的 成 员 同 一 个 晚 上 在 互 不 相 干 的 意 外 中 被 狼 袭 击 并 杀 死 的 机 率 还 要 小 分 支 引 用 指 明 一 次 提 交 最 直 接 的 方 法 是 有 一 个 指 向 它 的 分 支 引 用 这 样 你 就 可 以 在 任 意 一 个 Git 命 令 中 使 用 这 个 分 支 名 来 代 替 对 应 的 提 交 对 象 或 者 SHA-1 值 例 如, 你 想 要 查 看 一 个 分 支 的 最 后 一 次 提 交 的 对 象, 假 设 topic1 分 支 指 向 ca82a6d, 那 么 以 下 的 命 令 是 等 价 的 :

213 $ git show ca82a6dff817ec66f a $ git show topic1 如 果 你 想 知 道 某 个 分 支 指 向 哪 个 特 定 的 SHA-1, 或 者 想 看 任 何 一 个 例 子 中 被 简 写 的 SHA-1, 你 可 以 使 用 一 个 叫 做 rev-parse 的 Git 探 测 工 具 你 可 以 在 Git 内 部 原 理 中 查 看 更 多 关 于 探 测 工 具 的 信 息 简 单 来 说,revparse 是 为 了 底 层 操 作 而 不 是 日 常 操 作 设 计 的 不 过, 有 时 你 想 看 Git 现 在 到 底 处 于 什 么 状 态 时, 它 可 能 会 很 有 用 你 可 以 在 你 的 分 支 上 执 行 rev-parse $ git rev-parse topic1 ca82a6dff817ec66f a 引 用 日 志 当 你 在 工 作 时, Git 会 在 后 台 保 存 一 个 引 用 日 志 (reflog), 引 用 日 志 记 录 了 最 近 几 个 月 你 的 HEAD 和 分 支 引 用 所 指 向 的 历 史 你 可 以 使 用 git reflog 来 查 看 引 用 日 志 $ git reflog b HEAD@{0}: commit: fixed refs handling, added gc auto, updated d HEAD@{1}: merge phedders/rdocs: Merge made by recursive. 1c002dd HEAD@{2}: commit: added some blame and merge stuff 1c36188 HEAD@{3}: rebase -i (squash): updating HEAD 95df984 HEAD@{4}: commit: # This is a combination of two commits. 1c36188 HEAD@{5}: rebase -i (squash): updating HEAD 7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD 每 当 你 的 HEAD 所 指 向 的 位 置 发 生 了 变 化,Git 就 会 将 这 个 信 息 存 储 到 引 用 日 志 这 个 历 史 记 录 里 通 过 这 些 数 据, 你 可 以 很 方 便 地 获 取 之 前 的 提 交 历 史 如 果 你 想 查 看 仓 库 中 HEAD 在 五 次 前 的 所 指 向 的 提 交, 你 可 以 使 {n} 来 引 用 reflog 中 输 出 的 提 交 记 录 $ git show HEAD@{5} 你 同 样 可 以 使 用 这 个 语 法 来 查 看 某 个 分 支 在 一 定 时 间 前 的 位 置 例 如, 查 看 你 的 master 分 支 在 昨 天 的 时 候 指 向 了 哪 个 提 交, 你 可 以 输 入 $ git show master@{yesterday} 就 会 显 示 昨 天 该 分 支 的 顶 端 指 向 了 哪 个 提 交 这 个 方 法 只 对 还 在 你 引 用 日 志 里 的 数 据 有 用, 所 以 不 能 用 来 查 好 几

214 个 月 之 前 的 提 交 可 以 运 行 git log -g 来 查 看 类 似 于 git log 输 出 格 式 的 引 用 日 志 信 息 : $ git log -g master commit bc047d87bf7eac ae793478c50d3 Reflog: master@{0} (Scott Chacon <schacon@gmail.com>) Reflog message: commit: fixed refs handling, added gc auto, updated Author: Scott Chacon <schacon@gmail.com> Date: Fri Jan 2 18:32: fixed refs handling, added gc auto, updated tests commit d921970aadf03b3cf0e71becdaab3147ba71cdef Reflog: master@{1} (Scott Chacon <schacon@gmail.com>) Reflog message: merge phedders/rdocs: Merge made by recursive. Author: Scott Chacon <schacon@gmail.com> Date: Thu Dec 11 15:08: Merge commit 'phedders/rdocs' 值 得 注 意 的 是, 引 用 日 志 只 存 在 于 本 地 仓 库, 一 个 记 录 你 在 你 自 己 的 仓 库 里 做 过 什 么 的 日 志 其 他 人 拷 贝 的 仓 库 里 的 引 用 日 志 不 会 和 你 的 相 同 ; 而 你 新 克 隆 一 个 仓 库 的 时 候, 引 用 日 志 是 空 的, 因 为 你 在 仓 库 里 还 没 有 操 作 git show HEAD@{2.months.ago} 这 条 命 令 只 有 在 你 克 隆 了 一 个 项 目 至 少 两 个 月 时 才 会 有 用 如 果 你 是 五 分 钟 前 克 隆 的 仓 库, 那 么 它 将 不 会 有 结 果 返 回 祖 先 引 用 祖 先 引 用 是 另 一 种 指 明 一 个 提 交 的 方 式 如 果 你 在 引 用 的 尾 部 加 上 一 个 ^, Git 会 将 其 解 析 为 该 引 用 的 上 一 个 提 交 假 设 你 的 提 交 历 史 是 : $ git log --pretty=format:'%h %s' --graph * b fixed refs handling, added gc auto, updated tests * d Merge commit 'phedders/rdocs' \ * 35cfb2b Some rdoc changes * 1c002dd added some blame and merge stuff / * 1c36188 ignore *.gem * 9b29157 add open3_detach to gemspec file list 你 可 以 使 用 HEAD^ 来 查 看 上 一 个 提 交, 也 就 是 HEAD 的 父 提 交 :

215 $ git show HEAD^ commit d921970aadf03b3cf0e71becdaab3147ba71cdef Merge: 1c002dd... 35cfb2b... Author: Scott Chacon Date: Thu Dec 11 15:08: Merge commit 'phedders/rdocs' 你 也 可 以 在 ^ 后 面 添 加 一 个 数 字 例 如 d921970^2 代 表 d 的 第 二 父 提 交 这 个 语 法 只 适 用 于 合 并 (merge) 的 提 交, 因 为 合 并 提 交 会 有 多 个 父 提 交 第 一 父 提 交 是 你 合 并 时 所 在 分 支, 而 第 二 父 提 交 是 你 所 合 并 的 分 支 : $ git show d921970^ commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b Author: Scott Chacon <schacon@gmail.com> Date: Thu Dec 11 14:58: added some blame and merge stuff $ git show d921970^2 commit 35cfb2b795a55793d7cc56a6cc2060b4bb Author: Paul Hedderly <paul+git@mjr.org> Date: Wed Dec 10 22:22: Some rdoc changes 另 一 种 指 明 祖 先 提 交 的 方 法 是 ~ 同 样 是 指 向 第 一 父 提 交, 因 此 HEAD~ 和 HEAD^ 是 等 价 的 而 区 别 在 于 你 在 后 面 加 数 字 的 时 候 HEAD~2 代 表 第 一 父 提 交 的 第 一 父 提 交, 也 就 是 祖 父 提 交 Git 会 根 据 你 指 定 的 次 数 获 取 对 应 的 第 一 父 提 交 例 如, 在 之 前 的 列 出 的 提 交 历 史 中,HEAD~3 就 是 $ git show HEAD~3 commit 1c afb5fbcbea25b7c013f4e b8d Author: Tom Preston-Werner <tom@mojombo.com> Date: Fri Nov 7 13:47: ignore *.gem 也 可 以 写 成 HEAD^^^, 也 是 第 一 父 提 交 的 第 一 父 提 交 的 第 一 父 提 交 :

216 $ git show HEAD^^^ commit 1c afb5fbcbea25b7c013f4e b8d Author: Tom Preston-Werner Date: Fri Nov 7 13:47: ignore *.gem 你 也 可 以 组 合 使 用 这 两 个 语 法 你 可 以 通 过 HEAD~3^2 来 取 得 之 前 引 用 的 第 二 父 提 交 ( 假 设 它 是 一 个 合 并 提 交 ) 提 交 区 间 你 已 经 学 会 如 何 单 次 的 提 交, 现 在 来 看 看 如 何 指 明 一 定 区 间 的 提 交 当 你 有 很 多 分 支 时, 这 对 管 理 你 的 分 支 时 十 分 有 用, 你 可 以 用 提 交 区 间 来 解 决 这 个 分 支 还 有 哪 些 提 交 尚 未 合 并 到 主 分 支? 的 问 题 双 点 最 常 用 的 指 明 提 交 区 间 语 法 是 双 点 这 种 语 法 可 以 让 Git 选 出 在 一 个 分 支 中 而 不 在 另 一 个 分 支 中 的 提 交 例 如, 你 有 如 下 的 提 交 历 史 Example history for range selection. Figure 137. Example history for range selection. 你 想 要 查 看 experiment 分 支 中 还 有 哪 些 提 交 尚 未 被 合 并 入 master 分 支 你 可 以 使 用 master..experiment 来 让 Git 显 示 这 些 提 交 也 就 是 在 experiment 分 支 中 而 不 在 master 分 支 中 的 提 交 为 了 使 例 子 简 单 明 了, 我 使 用 了 示 意 图 中 提 交 对 象 的 字 母 来 代 替 真 实 日 志 的 输 出, 所 以 会 显 示 : $ git log master..experiment D C 反 过 来, 如 果 你 想 查 看 在 master 分 支 中 而 不 在 experiment 分 支 中 的 提 交, 你 只 要 交 换 分 支 名 即 可 experiment..master 会 显 示 在 master 分 支 中 而 不 在 experiment 分 支 中 的 提 交 : $ git log experiment..master F E

217 这 可 以 让 你 保 持 experiment 分 支 跟 随 最 新 的 进 度 以 及 查 看 你 即 将 合 并 的 内 容 另 一 个 常 用 的 场 景 是 查 看 你 即 将 推 送 到 远 端 的 内 容 : $ git log origin/master..head 这 个 命 令 会 输 出 在 你 当 前 分 支 中 而 不 在 远 程 origin 中 的 提 交 如 果 你 执 行 了 git push 并 且 你 的 当 前 分 支 正 在 跟 踪 origin/master,git log origin/master..head 所 输 出 的 提 交 将 会 被 传 输 到 远 端 服 务 器 如 果 你 留 空 了 其 中 的 一 边, Git 会 默 认 为 HEAD 例 如, git log origin/master.. 将 会 输 出 与 之 前 例 子 相 同 的 结 果 Git 使 用 HEAD 来 代 替 留 空 的 一 边 多 点 双 点 语 法 很 好 用, 但 有 时 候 你 可 能 需 要 两 个 以 上 的 分 支 才 能 确 定 你 所 需 要 的 修 订, 比 如 查 看 哪 些 提 交 是 被 包 含 在 某 些 分 支 中 的 一 个, 但 是 不 在 你 当 前 的 分 支 上 Git 允 许 你 在 任 意 引 用 前 加 上 ^ 字 符 或 者 --not 来 指 明 你 不 希 望 提 交 被 包 含 其 中 的 分 支 因 此 下 列 3 个 命 令 是 等 价 的 : $ git log refa..refb $ git log ^refa refb $ git log refb --not refa 这 个 语 法 很 好 用, 因 为 你 可 以 在 查 询 中 指 定 超 过 两 个 的 引 用, 这 是 双 点 语 法 无 法 实 现 的 比 如, 你 想 查 看 所 有 被 refa 或 refb 包 含 的 但 是 不 被 refc 包 含 的 提 交, 你 可 以 输 入 下 面 中 的 任 意 一 个 命 令 $ git log refa refb ^refc $ git log refa refb --not refc 这 就 构 成 了 一 个 十 分 强 大 的 修 订 查 询 系 统, 你 可 以 通 过 它 来 查 看 你 的 分 支 里 包 含 了 哪 些 东 西 三 点 最 后 一 种 主 要 的 区 间 选 择 语 法 是 三 点, 这 个 语 法 可 以 选 择 出 被 两 个 引 用 中 的 一 个 包 含 但 又 不 被 两 者 同 时 包 含 的 提 交 再 看 看 之 前 双 点 例 子 中 的 提 交 历 史 如 果 你 想 看 master 或 者 experiment 中 包 含 的 但 不 是 两 者 共 有 的 提 交, 你 可 以 执 行 $ git log master...experiment F E D C 这 和 通 常 log 按 日 期 排 序 的 输 出 一 样, 仅 仅 给 出 了 4 个 提 交 的 信 息

218 这 种 情 形 下,log 命 令 的 一 个 常 用 参 数 是 --left-right, 它 会 显 示 每 个 提 交 到 底 处 于 哪 一 侧 的 分 支 这 会 让 输 出 数 据 更 加 清 晰 $ git log --left-right master...experiment < F < E > D > C 有 了 这 些 工 具, 你 就 可 以 十 分 方 便 地 查 看 你 Git 仓 库 中 的 提 交 交 互 式 暂 存 Git 自 带 的 一 些 脚 本 可 以 使 在 命 令 行 下 工 作 更 容 易 本 节 的 几 个 互 交 命 令 可 以 帮 助 你 将 文 件 的 特 定 部 分 组 合 成 提 交 当 你 修 改 一 组 文 件 后, 希 望 这 些 改 动 能 放 到 若 干 提 交 而 不 是 混 杂 在 一 起 成 为 一 个 提 交 时, 这 几 个 工 具 会 非 常 有 用 通 过 这 种 方 式, 可 以 确 保 提 交 是 逻 辑 上 独 立 的 变 更 集, 同 时 也 会 使 其 他 开 发 者 在 与 你 工 作 时 很 容 易 地 审 核 如 果 运 行 git add 时 使 用 -i 或 者 --interactive 选 项,Git 将 会 进 入 一 个 交 互 式 终 端 模 式, 显 示 类 似 下 面 的 东 西 : $ git add -i staged unstaged path 1: unchanged +0/-1 TODO 2: unchanged +1/-1 index.html 3: unchanged +5/-1 lib/simplegit.rb *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now> 可 以 看 到 这 个 命 令 以 非 常 不 同 的 视 图 显 示 了 暂 存 区 - 基 本 上 与 git status 是 相 同 的 信 息, 但 是 更 简 明 扼 要 一 些 它 将 暂 存 的 修 改 列 在 左 侧, 未 暂 存 的 修 改 列 在 右 侧 在 这 块 区 域 后 是 命 令 区 域 在 这 里 你 可 以 做 一 些 工 作, 包 括 暂 存 文 件 取 消 暂 存 文 件 暂 存 文 件 的 一 部 分 添 加 未 被 追 踪 的 文 件 查 看 暂 存 内 容 的 区 别 暂 存 与 取 消 暂 存 文 件 如 果 在 What now> 提 示 符 后 键 入 2 或 u, 脚 本 将 会 提 示 想 要 暂 存 哪 个 文 件 :

219 What now> 2 staged unstaged path 1: unchanged +0/-1 TODO 2: unchanged +1/-1 index.html 3: unchanged +5/-1 lib/simplegit.rb Update>> 要 暂 存 TODO 与 index.html 文 件, 可 以 输 入 数 字 : Update>> 1,2 staged unstaged path * 1: unchanged +0/-1 TODO * 2: unchanged +1/-1 index.html 3: unchanged +5/-1 lib/simplegit.rb Update>> 每 个 文 件 前 面 的 * 意 味 着 选 中 的 文 件 将 会 被 暂 存 如 果 在 Update>> 提 示 符 后 不 输 入 任 何 东 西 并 直 接 按 回 车,Git 将 会 暂 存 之 前 选 择 的 文 件 : Update>> updated 2 paths *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now> 1 staged unstaged path 1: +0/-1 nothing TODO 2: +1/-1 nothing index.html 3: unchanged +5/-1 lib/simplegit.rb 现 在 可 以 看 到 TODO 与 index.html 文 件 已 经 被 暂 存 而 simplegit.rb 文 件 还 未 被 暂 存 如 果 这 时 想 要 取 消 暂 存 TODO 文 件, 使 用 3 或 r( 撤 消 ) 选 项 :

220 *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now> 3 staged unstaged path 1: +0/-1 nothing TODO 2: +1/-1 nothing index.html 3: unchanged +5/-1 lib/simplegit.rb Revert>> 1 staged unstaged path * 1: +0/-1 nothing TODO 2: +1/-1 nothing index.html 3: unchanged +5/-1 lib/simplegit.rb Revert>> [enter] reverted one path 再 次 查 看 Git 状 态, 可 以 看 到 已 经 取 消 暂 存 TODO 文 件 : *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now> 1 staged unstaged path 1: unchanged +0/-1 TODO 2: +1/-1 nothing index.html 3: unchanged +5/-1 lib/simplegit.rb 如 果 想 要 查 看 已 暂 存 内 容 的 区 别, 可 以 使 用 6 或 d( 区 别 ) 命 令 它 会 显 示 暂 存 文 件 的 一 个 列 表, 可 以 从 中 选 择 想 要 查 看 的 暂 存 区 别 这 跟 你 在 命 令 行 指 定 git diff --cached 非 常 相 似 :

221 *** Commands *** 1: status 2: update 3: revert 4: add untracked 5: patch 6: diff 7: quit 8: help What now> 6 staged unstaged path 1: +1/-1 nothing index.html Review diff>> 1 diff --git a/index.html b/index.html index 4d f a/index.html ,7 Date Finder <p id="out">...</p> -<div id="footer">contact : support@github.com</div> +<div id="footer">contact : .support@github.com</div> <script type="text/javascript"> 通 过 这 些 基 本 命 令, 可 以 使 用 交 互 式 添 加 模 式 来 轻 松 地 处 理 暂 存 区 暂 存 补 丁 Git 也 可 以 暂 存 文 件 的 特 定 部 分 例 如, 如 果 在 simplegit.rb 文 件 中 做 了 两 处 修 改, 但 只 想 要 暂 存 其 中 的 一 个 而 不 是 另 一 个,Git 会 帮 你 轻 松 地 完 成 从 交 互 式 提 示 符 中, 输 入 5 或 p( 补 丁 ) Git 会 询 问 你 想 要 部 分 暂 存 哪 些 文 件 ; 然 后, 对 已 选 择 文 件 的 每 一 个 部 分, 它 都 会 一 个 个 地 显 示 文 件 区 别 并 询 问 你 是 否 想 要 暂 存 它 们 : diff --git a/lib/simplegit.rb b/lib/simplegit.rb index dd5ecc e a/lib/simplegit.rb ,7 class SimpleGit end def log(treeish = 'master') - command("git log -n 25 #{treeish}") + command("git log -n 30 #{treeish}") end def blame(path) Stage this hunk [y,n,a,d,/,j,j,g,e,?]? 这 时 有 很 多 选 项 输 入? 显 示 所 有 可 以 使 用 的 命 令 列 表 :

222 Stage this hunk [y,n,a,d,/,j,j,g,e,?]?? y - stage this hunk n - do not stage this hunk a - stage this and all the remaining hunks in the file d - do not stage this hunk nor any of the remaining hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk K - leave this hunk undecided, see previous hunk s - split the current hunk into smaller hunks e - manually edit the current hunk? - print help 通 常 情 况 下 可 以 输 入 y 或 n 来 选 择 是 否 要 暂 存 每 一 个 区 块, 当 然, 暂 存 特 定 文 件 中 的 所 有 部 分 或 为 之 后 的 选 择 跳 过 一 个 区 块 也 是 非 常 有 用 的 如 果 你 只 暂 存 文 件 的 一 部 分, 状 态 输 出 可 能 会 像 下 面 这 样 : What now> 1 staged unstaged path 1: unchanged +0/-1 TODO 2: +1/-1 nothing index.html 3: +1/-1 +4/-0 lib/simplegit.rb simplegit.rb 文 件 的 状 态 很 有 趣 它 显 示 出 若 干 行 被 暂 存 与 若 干 行 未 被 暂 存 已 经 部 分 地 暂 存 了 这 个 文 件 在 这 时, 可 以 退 出 交 互 式 添 加 脚 本 并 且 运 行 git commit 来 提 交 部 分 暂 存 的 文 件 也 可 以 不 必 在 交 互 式 添 加 模 式 中 做 部 分 文 件 暂 存 - 可 以 在 命 令 行 中 使 用 git add -p 或 git add --patch 来 启 动 同 样 的 脚 本 更 进 一 步 地, 可 以 使 用 reset --patch 命 令 的 补 丁 模 式 来 部 分 重 置 文 件, 通 过 checkout --patch 命 令 来 部 分 检 出 文 件 与 stash save --patch 命 令 来 部 分 暂 存 文 件 我 们 将 会 在 接 触 这 些 命 令 的 高 级 使 用 方 法 时 了 解 更 多 详 细 信 息 储 藏 与 清 理 有 时, 当 你 在 项 目 的 一 部 分 上 已 经 工 作 一 段 时 间 后, 所 有 东 西 都 进 入 了 混 乱 的 状 态, 而 这 时 你 想 要 切 换 到 另 一 个 分 支 做 一 点 别 的 事 情 问 题 是, 你 不 想 仅 仅 因 为 过 会 儿 回 到 这 一 点 而 为 做 了 一 半 的 工 作 创 建 一 次 提 交 针 对 这 个 问 题 的 答 案 是 git stash 命 令 储 藏 会 处 理 工 作 目 录 的 脏 的 状 态 - 即, 修 改 的 跟 踪 文 件 与 暂 存 改 动 - 然 后 将 未 完 成 的 修 改 保 存 到 一 个 栈 上, 而 你 可 以 在 任 何 时 候 重 新 应 用 这 些 改 动

223 储 藏 工 作 为 了 演 示, 进 入 项 目 并 改 动 几 个 文 件, 然 后 可 能 暂 存 其 中 的 一 个 改 动 如 果 运 行 git status, 可 以 看 到 有 改 动 的 状 态 : $ git status Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: index.html Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: lib/simplegit.rb 现 在 想 要 切 换 分 支, 但 是 还 不 想 要 提 交 之 前 的 工 作 ; 所 以 储 藏 修 改 将 新 的 储 藏 推 送 到 栈 上, 运 行 git stash 或 git stash save: $ git stash Saved working directory and index state \ "WIP on master: 049d078 added the index file" HEAD is now at 049d078 added the index file (To restore them type "git stash apply") 工 作 目 录 是 干 净 的 了 : $ git status # On branch master nothing to commit, working directory clean 在 这 时, 你 能 够 轻 易 地 切 换 分 支 并 在 其 他 地 方 工 作 ; 你 的 修 改 被 存 储 在 栈 上 要 查 看 储 藏 的 东 西, 可 以 使 用 git stash list: $ git stash list stash@{0}: WIP on master: 049d078 added the index file stash@{1}: WIP on master: c Revert "added file_size" stash@{2}: WIP on master: 21d80a5 added number to log 在 本 例 中, 有 两 个 之 前 做 的 储 藏, 所 以 你 接 触 到 了 三 个 不 同 的 储 藏 工 作 可 以 通 过 原 来 stash 命 令 的 帮 助 提 示 中

224 的 命 令 将 你 刚 刚 储 藏 的 工 作 重 新 应 用 :git stash apply 如 果 想 要 应 用 其 中 一 个 更 旧 的 储 藏, 可 以 通 过 名 字 指 定 它, 像 这 样 :git stash apply 如 果 不 指 定 一 个 储 藏,Git 认 为 指 定 的 是 最 近 的 储 藏 : $ git stash apply # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # # modified: index.html # modified: lib/simplegit.rb # 可 以 看 到 Git 重 新 修 改 了 当 你 保 存 储 藏 时 撤 消 的 文 件 在 本 例 中, 当 尝 试 应 用 储 藏 时 有 一 个 干 净 的 工 作 目 录, 并 且 尝 试 将 它 应 用 在 保 存 它 时 所 在 的 分 支 ; 但 是 有 一 个 干 净 的 工 作 目 录 与 应 用 在 同 一 分 支 并 不 是 成 功 应 用 储 藏 的 充 分 必 要 条 件 可 以 在 一 个 分 支 上 保 存 一 个 储 藏, 切 换 到 另 一 个 分 支, 然 后 尝 试 重 新 应 用 这 些 修 改 当 应 用 储 藏 时 工 作 目 录 中 也 可 以 有 修 改 与 未 提 交 的 文 件 - 如 果 有 任 何 东 西 不 能 干 净 地 应 用,Git 会 产 生 合 并 冲 突 文 件 的 改 动 被 重 新 应 用 了, 但 是 之 前 暂 存 的 文 件 却 没 有 重 新 暂 存 想 要 那 样 的 话, 必 须 使 用 --index 选 项 来 运 行 git stash apply 命 令, 来 尝 试 重 新 应 用 暂 存 的 修 改 如 果 已 经 那 样 做 了, 那 么 你 将 回 到 原 来 的 位 置 : $ git stash apply --index # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb # 应 用 选 项 只 会 尝 试 应 用 暂 存 的 工 作 - 在 堆 栈 上 还 有 它 可 以 运 行 git stash drop 加 上 将 要 移 除 的 储 藏 的 名 字 来 移 除 它 : $ git stash list stash@{0}: WIP on master: 049d078 added the index file stash@{1}: WIP on master: c Revert "added file_size" stash@{2}: WIP on master: 21d80a5 added number to log $ git stash drop stash@{0} Dropped stash@{0} (364e91f3f268f0900bc3ee613f9f733e82aaed43)

225 也 可 以 运 行 git stash pop 来 应 用 储 藏 然 后 立 即 从 栈 上 扔 掉 它 创 造 性 的 储 藏 有 几 个 储 藏 的 变 种 可 能 也 很 有 用 第 一 个 非 常 流 行 的 选 项 是 stash save 命 令 的 --keep-index 选 项 它 告 诉 Git 不 要 储 藏 任 何 你 通 过 git add 命 令 已 暂 存 的 东 西 当 你 做 了 几 个 改 动 并 只 想 提 交 其 中 的 一 部 分, 过 一 会 儿 再 回 来 处 理 剩 余 改 动 时, 这 个 功 能 会 很 有 用 $ git status -s M index.html M lib/simplegit.rb $ git stash --keep-index Saved working directory and index state WIP on master: 1b65b17 added the index file HEAD is now at 1b65b17 added the index file $ git status -s M index.html 另 一 个 经 常 使 用 储 藏 来 做 的 事 情 是 像 储 藏 跟 踪 文 件 一 样 储 藏 未 跟 踪 文 件 默 认 情 况 下,git stash 只 会 储 藏 已 经 在 索 引 中 的 文 件 如 果 指 定 --include-untracked 或 -u 标 记,Git 也 会 储 藏 任 何 创 建 的 未 跟 踪 文 件 $ git status -s M index.html M lib/simplegit.rb?? new-file.txt $ git stash -u Saved working directory and index state WIP on master: 1b65b17 added the index file HEAD is now at 1b65b17 added the index file $ git status -s $ 最 终, 如 果 指 定 了 --patch 标 记,Git 不 会 储 藏 所 有 修 改 过 的 任 何 东 西, 但 是 会 交 互 式 地 提 示 哪 些 改 动 想 要 储 藏 哪 些 改 动 需 要 保 存 在 工 作 目 录 中

226 $ git stash --patch diff --git a/lib/simplegit.rb b/lib/simplegit.rb index 66d332e..8bb a/lib/simplegit.rb +++ b/lib/simplegit.rb -16,6 +16,10 class SimpleGit return `#{git_cmd} 2>&1`.chomp end end + + def show(treeish = 'master') + command("git show #{treeish}") + end end test 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 testchanges Switched to a new branch "testchanges" # On branch testchanges # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb # Dropped refs/stash@{0} (f0dfc4d5dc332d1cee34a634182e168c4efc3359) 这 是 在 新 分 支 轻 松 恢 复 储 藏 工 作 并 继 续 工 作 的 一 个 很 不 错 的 途 径

227 清 理 工 作 目 录 对 于 工 作 目 录 中 一 些 工 作 或 文 件, 你 想 做 的 也 许 不 是 储 藏 而 是 移 除 git clean 命 令 会 帮 你 做 这 些 事 有 一 些 通 用 的 原 因 比 如 说 为 了 移 除 由 合 并 或 外 部 工 具 生 成 的 东 西, 或 是 为 了 运 行 一 个 干 净 的 构 建 而 移 除 之 前 构 建 的 残 留 你 需 要 谨 慎 地 使 用 这 个 命 令, 因 为 它 被 设 计 为 从 工 作 目 录 中 移 除 未 被 追 踪 的 文 件 如 果 你 改 变 主 意 了, 你 也 不 一 定 能 找 回 来 那 些 文 件 的 内 容 一 个 更 安 全 的 选 项 是 运 行 git stash --all 来 移 除 每 一 样 东 西 并 存 放 在 栈 中 你 可 以 使 用 `git clean` 命 令 去 除 冗 余 文 件 或 者 清 理 工 作 目 录 使 用 `git clean -f -d` 命 令 来 移 除 工 作 目 录 中 所 有 未 追 踪 的 文 件 以 及 空 的 子 目 录 -f 意 味 着 强 制 或 确 定 移 除 如 果 只 是 想 要 看 看 它 会 做 什 么, 可 以 使 用 -n 选 项 来 运 行 命 令, 这 意 味 着 做 一 次 演 习 然 后 告 诉 你 将 要 移 除 什 么 $ git clean -d -n Would remove test.o Would remove tmp/ 默 认 情 况 下,git clean 命 令 只 会 移 除 没 有 忽 略 的 未 跟 踪 文 件 任 何 与.gitiignore 或 其 他 忽 略 文 件 中 的 模 式 匹 配 的 文 件 都 不 会 被 移 除 如 果 你 也 想 要 移 除 那 些 文 件, 例 如 为 了 做 一 次 完 全 干 净 的 构 建 而 移 除 所 有 由 构 建 生 成 的.o 文 件, 可 以 给 clean 命 令 增 加 一 个 -x 选 项 $ git status -s M lib/simplegit.rb?? build.tmp?? tmp/ $ git clean -n -d Would remove build.tmp Would remove tmp/ $ git clean -n -d -x Would remove build.tmp Would remove test.o Would remove tmp/ 如 果 不 知 道 git clean 命 令 将 会 做 什 么, 在 将 -n 改 为 -f 来 真 正 做 之 前 总 是 先 用 -n 来 运 行 它 做 双 重 检 查 另 一 个 小 心 处 理 过 程 的 方 式 是 使 用 -i 或 interactive 标 记 来 运 行 它 这 将 会 以 交 互 模 式 运 行 clean 命 令

228 $ git clean -x -i Would remove the following items: build.tmp test.o *** Commands *** 1: clean 2: filter by pattern 3: select by numbers 4: ask each 5: quit 6: help What now> 这 种 方 式 下 可 以 分 别 地 检 查 每 一 个 文 件 或 者 交 互 地 指 定 删 除 的 模 式 签 署 工 作 Git 虽 然 是 密 码 级 安 全 的, 但 它 不 是 万 无 一 失 的 如 果 你 从 因 特 网 上 的 其 他 人 那 里 拿 取 工 作, 并 且 想 要 验 证 提 交 是 不 是 真 正 地 来 自 于 可 信 来 源,Git 提 供 了 几 种 通 过 GPG 来 签 署 和 验 证 工 作 的 方 式 GPG 介 绍 首 先, 在 开 始 签 名 之 前 你 需 要 先 配 置 GPG 并 安 装 个 人 密 钥 $ gpg --list-keys /Users/schacon/.gnupg/pubring.gpg pub 2048R/0A46826A uid Scott Chacon (Git signing key) <schacon@gmail.com> sub 2048R/874529A 如 果 你 还 没 有 安 装 一 个 密 钥, 可 以 使 用 gpg --gen-key 生 成 一 个 gpg --gen-key 一 旦 你 有 一 个 可 以 签 署 的 私 钥, 可 以 通 过 设 置 Git 的 user.signingkey 选 项 来 签 署 git config --global user.signingkey 0A46826A 现 在 Git 默 认 使 用 你 的 密 钥 来 签 署 标 签 与 提 交 签 署 标 签 如 果 已 经 设 置 好 一 个 GPG 私 钥, 可 以 使 用 它 来 签 署 新 的 标 签 所 有 需 要 做 的 只 是 使 用 -s 代 替 -a 即 可 :

229 $ git tag -s v1.5 -m 'my signed 1.5 tag' You need a passphrase to unlock the secret key for user: "Ben Straub <ben@straub.cc>" 2048-bit RSA key, ID EB, created 如 果 在 那 个 标 签 上 运 行 git show, 会 看 到 你 的 GPG 签 名 附 属 在 后 面 : $ git show v1.5 tag v1.5 Tagger: Ben Straub <ben@straub.cc> Date: Sat May 3 20:29: my signed 1.5 tag -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iqecbaabagagbqjtzbqlaaojef0+sviabddrzbqh/09pfe51kpvplanr6q1v4/ut LQxfojUWiLQdg2ESJItkcuweYg+kc3HCyFejeDIBw9dpXt00rY26p05qrpnG+85b hm1/pswpplubsr+ocidj5gmc2r2ieksfv2fjbnw8iwaxvlowzrf8b0mfqx/ytmbm ecorc4ixzqu7tuprihslbnkfvfcimnsdesvzcpwahl7h8wj6hhqepmlm9layqnkp 8S5B/1SSQuEAjRZgI4IexpZoeKGVDptPHxLLS38fozsyi0QyDyzEgJxcJQVMXxVi RUysgqjcpT8+iQM1PblGfHR4XAhuOqN5Fx06PSaFZhqvWFezJ28/CLyX5q+oIVk= =EFTF -----END PGP SIGNATURE----- commit ca82a6dff817ec66f a Author: Scott Chacon <schacon@ge .com> Date: Mon Mar 17 21:52: changed the version number 验 证 标 签 要 验 证 一 个 签 署 的 标 签, 可 以 运 行 git tag -v [tag-name] 这 个 命 令 使 用 GPG 来 验 证 签 名 为 了 验 证 能 正 常 工 作, 签 署 者 的 公 钥 需 要 在 你 的 钥 匙 链 中

230 $ git tag -v v object babd8ee7ea23e6a5c392bb739348b1eb61 type commit tag v tagger Junio C Hamano <junkio@cox.net> GIT Minor fixes since 1.4.2, including git-mv and git-http with alternates. gpg: Signature made Wed Sep 13 02:08: PDT using DSA key ID F3119B9A gpg: Good signature from "Junio C Hamano <junkio@cox.net>" gpg: aka "[jpeg image of size 1513]" Primary key fingerprint: A E066 C9A7 4A7D C0C6 D9A4 F311 9B9A 如 果 没 有 签 署 者 的 公 钥, 那 么 你 将 会 得 到 类 似 下 面 的 东 西 : gpg: Signature made Wed Sep 13 02:08: PDT using DSA key ID F3119B9A gpg: Can't check signature: public key not found error: could not verify the tag 'v ' 签 署 提 交 在 最 新 版 本 的 Git 中 (v1.7.9 及 以 上 ), 也 可 以 签 署 个 人 提 交 如 果 相 对 于 标 签 而 言 你 对 直 接 签 署 到 提 交 更 感 兴 趣 的 话, 所 有 要 做 的 只 是 增 加 一 个 -S 到 git commit 命 令 $ git commit -a -S -m 'signed commit' You need a passphrase to unlock the secret key for user: "Scott Chacon (Git signing key) <schacon@gmail.com>" 2048-bit RSA key, ID 0A46826A, created [master 5c3386c] signed commit 4 files changed, 4 insertions(+), 24 deletions(-) rewrite Rakefile (100%) create mode lib/git.rb git log 也 有 一 个 --show-signature 选 项 来 查 看 及 验 证 这 些 签 名

231 $ git log --show-signature -1 commit 5c3386cf54bba0a33a32da706aa52bc c2 gpg: Signature made Wed Jun 4 19:49: PDT using RSA key ID 0A46826A gpg: Good signature from "Scott Chacon (Git signing key) <schacon@gmail.com>" Author: Scott Chacon <schacon@gmail.com> Date: Wed Jun 4 19:49: signed commit 另 外, 也 可 以 配 置 git log 来 验 证 任 何 找 到 的 签 名 并 将 它 们 以 %G? 格 式 列 在 输 出 中 $ git log --pretty="format:%h %G? %an %s" 5c3386c G Scott Chacon signed commit ca82a6d N Scott Chacon changed the version number 085bb3b N Scott Chacon removed unnecessary test code a11bef0 N Scott Chacon first commit 这 里 我 们 可 以 看 到 只 有 最 后 一 次 提 交 是 签 署 并 有 效 的, 而 之 前 的 提 交 都 不 是 在 Git 及 以 后 的 版 本 中, git merge 与 git pull 可 以 使 用 --verify-signatures 选 项 来 检 查 并 拒 绝 没 有 携 带 可 信 GPG 签 名 的 提 交 如 果 使 用 这 个 选 项 来 合 并 一 个 包 含 未 签 名 或 有 效 的 提 交 的 分 支 时, 合 并 不 会 生 效 $ git merge --verify-signatures non-verify fatal: Commit ab06180 does not have a GPG signature. 如 果 合 并 包 含 的 只 有 有 效 的 签 名 的 提 交, 合 并 命 令 会 提 示 所 有 的 签 名 它 已 经 检 查 过 了 然 后 会 继 续 向 前 $ git merge --verify-signatures signed-branch Commit 13ad65e has a good GPG signature by Scott Chacon (Git signing key) <schacon@gmail.com> Updating 5c3386c..13ad65e Fast-forward README file changed, 2 insertions(+) 也 可 以 给 git merge 命 令 附 加 -S 选 项 来 签 署 自 己 生 成 的 合 并 提 交 下 面 的 例 子 演 示 了 验 证 将 要 合 并 的 分 支 的 每 一 个 提 交 都 是 签 名 的 并 且 签 署 最 后 生 成 的 合 并 提 交

232 $ git merge --verify-signatures -S signed-branch Commit 13ad65e has a good GPG signature by Scott Chacon (Git signing key) <schacon@gmail.com> You need a passphrase to unlock the secret key for user: "Scott Chacon (Git signing key) <schacon@gmail.com>" 2048-bit RSA key, ID 0A46826A, created Merge made by the 'recursive' strategy. README file changed, 2 insertions(+) 每 个 人 必 须 签 署 签 署 标 签 与 提 交 很 棒, 但 是 如 果 决 定 在 正 常 的 工 作 流 程 中 使 用 它, 你 必 须 确 保 团 队 中 的 每 一 个 人 都 理 解 如 何 这 样 做 如 果 没 有, 你 将 会 花 费 大 量 时 间 帮 助 其 他 人 找 出 并 用 签 名 的 版 本 重 写 提 交 在 采 用 签 署 成 为 标 准 工 作 流 程 的 一 部 分 前, 确 保 你 完 全 理 解 GPG 及 签 署 带 来 的 好 处 搜 索 无 论 仓 库 里 的 代 码 量 有 多 少, 你 经 常 需 要 查 找 一 个 函 数 是 在 哪 里 调 用 或 者 定 义 的, 或 者 一 个 方 法 的 变 更 历 史 Git 提 供 了 两 个 有 用 的 工 具 来 快 速 地 从 它 的 数 据 库 中 浏 览 代 码 和 提 交 我 们 来 简 单 的 看 一 下 Git Grep Git 提 供 了 一 个 grep 命 令, 你 可 以 很 方 便 地 从 提 交 历 史 或 者 工 作 目 录 中 查 找 一 个 字 符 串 或 者 正 则 表 达 式 我 们 用 Git 本 身 源 代 码 的 查 找 作 为 例 子 默 认 情 况 下 Git 会 查 找 你 工 作 目 录 的 文 件 你 可 以 传 入 -n 参 数 来 输 出 Git 所 找 到 的 匹 配 行 行 号

233 $ git grep -n gmtime_r compat/gmtime.c:3:#undef gmtime_r compat/gmtime.c:8: return git_gmtime_r(timep, &result); compat/gmtime.c:11:struct tm *git_gmtime_r(const time_t *timep, struct tm *result) compat/gmtime.c:16: ret = gmtime_r(timep, result); compat/mingw.c:606:struct tm *gmtime_r(const time_t *timep, struct tm *result) compat/mingw.h:162:struct tm *gmtime_r(const time_t *timep, struct tm *result); date.c:429: if (gmtime_r(&now, &now_tm)) date.c:492: if (gmtime_r(&time, tm)) { git-compat-util.h:721:struct tm *git_gmtime_r(const time_t *, struct tm *); git-compat-util.h:723:#define gmtime_r git_gmtime_r grep 命 令 有 一 些 有 趣 的 选 项 例 如, 你 可 以 使 用 --count 选 项 来 使 Git 输 出 概 述 的 信 息, 仅 仅 包 括 哪 些 文 件 包 含 匹 配 以 及 每 个 文 件 包 含 了 多 少 个 匹 配 $ git grep --count gmtime_r compat/gmtime.c:4 compat/mingw.c:1 compat/mingw.h:1 date.c:2 git-compat-util.h:2 如 果 你 想 看 匹 配 的 行 是 属 于 哪 一 个 方 法 或 者 函 数, 你 可 以 传 入 -p 选 项 : $ git grep -p gmtime_r *.c date.c=static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) date.c: if (gmtime_r(&now, &now_tm)) date.c=static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) date.c: if (gmtime_r(&time, tm)) { 在 这 里 我 们 可 以 看 到 在 date.c 文 件 中 有 match_multi_number 和 match_digit 两 个 函 数 调 用 了 gmtime_r 你 还 可 以 使 用 --and 标 志 来 查 看 复 杂 的 字 符 串 组 合, 也 就 是 在 同 一 行 同 时 包 含 多 个 匹 配 比 如, 我 们 要 查 看 在 旧 版 本 的 Git 代 码 库 中 定 义 了 常 量 名 包 含 LINK 或 者 BUF_MAX 这 两 个 字 符 串 所 在 的 行

234 这 里 我 们 也 用 到 了 --break 和 --heading 选 项 来 使 输 出 更 加 容 易 阅 读 $ git grep --break --heading \ -n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0 v1.8.0:builtin/index-pack.c 62:#define FLAG_LINK (1u<<20) v1.8.0:cache.h 73:#define S_IFGITLINK :#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) v1.8.0:environment.c 54:#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS v1.8.0:strbuf.c 326:#define STRBUF_MAXLINK (2*PATH_MAX) v1.8.0:symlinks.c 53:#define FL_SYMLINK (1 << 2) v1.8.0:zlib.c 30:/* #define ZLIB_BUF_MAX ((uint)-1) */ 31:#define ZLIB_BUF_MAX ((uint) 1024 * 1024 * 1024) /* 1GB */ 相 比 于 一 些 常 用 的 搜 索 命 令 比 如 grep 和 ack,git grep 命 令 有 一 些 的 优 点 第 一 就 是 速 度 非 常 快, 第 二 是 你 不 仅 仅 可 以 可 以 搜 索 工 作 目 录, 还 可 以 搜 索 任 意 的 Git 树 在 上 一 个 例 子 中, 我 们 在 一 个 旧 版 本 的 Git 源 代 码 中 查 找, 而 不 是 当 前 检 出 的 版 本 Git 日 志 搜 索 或 许 你 不 想 知 道 某 一 项 在 哪 里, 而 是 想 知 道 是 什 么 时 候 存 在 或 者 引 入 的 git log 命 令 有 许 多 强 大 的 工 具 可 以 通 过 提 交 信 息 甚 至 是 diff 的 内 容 来 找 到 某 个 特 定 的 提 交 例 如, 如 果 我 们 想 找 到 ZLIB_BUF_MAX 常 量 是 什 么 时 候 引 入 的, 我 们 可 以 使 用 -S 选 项 来 显 示 新 增 和 删 除 该 字 符 串 的 提 交 $ git log -SZLIB_BUF_MAX --oneline e01503b zlib: allow feeding more than 4GB in one go ef49a7a zlib: zlib can only process 4GB at a time 如 果 我 们 查 看 这 些 提 交 的 diff, 我 们 可 以 看 到 在 ef49a7a 这 个 提 交 引 入 了 常 量, 并 且 在 e01503b 这 个 提 交 中 被 修 改 了 如 果 你 希 望 得 到 更 精 确 的 结 果, 你 可 以 使 用 -G 选 项 来 使 用 正 则 表 达 式 搜 索

235 行 日 志 搜 索 行 日 志 搜 索 是 另 一 个 相 当 高 级 并 且 有 用 的 日 志 搜 索 功 能 这 是 一 个 最 近 新 增 的 不 太 知 名 的 功 能, 但 却 是 十 分 有 用 在 git log 后 加 上 -L 选 项 即 可 调 用, 它 可 以 展 示 代 码 中 一 行 或 者 一 个 函 数 的 历 史 例 如, 假 设 我 们 想 查 看 zlib.c 文 件 中 `git_deflate_bound` 函 数 的 每 一 次 变 更, 我 们 可 以 执 行 git log -L :git_deflate_bound:zlib.c Git 会 尝 试 找 出 这 个 函 数 的 范 围, 然 后 查 找 历 史 记 录, 并 且 显 示 从 函 数 创 建 之 后 一 系 列 变 更 对 应 的 补 丁 $ git log -L :git_deflate_bound:zlib.c commit ef49a7a0126d64359c974b4b3b71d7ad42ee3bca Author: Junio C Hamano <gitster@pobox.com> Date: Fri Jun 10 11:52: zlib: zlib can only process 4GB at a time diff --git a/zlib.c b/zlib.c --- a/zlib.c ,5 -unsigned long git_deflate_bound(z_streamp strm, unsigned long size) +unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) { - return deflatebound(strm, size); + return deflatebound(&strm->z, size); } commit 225a6f1068f71723a910e8565db4e252b3ca21fa Author: Junio C Hamano <gitster@pobox.com> Date: Fri Jun 10 11:18: zlib: wrap deflatebound() too diff --git a/zlib.c b/zlib.c --- a/zlib.c ,0 +unsigned long git_deflate_bound(z_streamp strm, unsigned long size) +{ + return deflatebound(strm, size); +} + 如 果 Git 无 法 计 算 出 如 何 匹 配 你 代 码 中 的 函 数 或 者 方 法, 你 可 以 提 供 一 个 正 则 表 达 式 例 如, 这 个 命 令 和 上 面 的 是 等 同 的 :git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c 你 也 可 以 提 供 单 行 或 者 一 个 范 围 的 行 号 来 获 得 相 同 的 输 出

236 重 写 历 史 许 多 时 候, 在 使 用 Git 时, 可 能 会 因 为 某 些 原 因 想 要 修 正 提 交 历 史 Git 很 棒 的 一 点 是 它 允 许 你 在 最 后 时 刻 做 决 定 你 可 以 在 将 暂 存 区 内 容 提 交 前 决 定 哪 些 文 件 进 入 提 交, 可 以 通 过 stash 命 令 来 决 定 不 与 某 些 内 容 工 作, 也 可 以 重 写 已 经 发 生 的 提 交 就 像 它 们 以 另 一 种 方 式 发 生 的 一 样 这 可 能 涉 及 改 变 提 交 的 顺 序, 改 变 提 交 中 的 信 息 或 修 改 文 件, 将 提 交 压 缩 或 是 拆 分, 或 完 全 地 移 除 提 交 - 在 将 你 的 工 作 成 果 与 他 人 共 享 之 前 在 本 节 中, 你 可 以 学 到 如 何 完 成 这 些 非 常 有 用 的 工 作, 这 样 在 与 他 人 分 享 你 的 工 作 成 果 时 你 的 提 交 历 史 将 如 你 所 愿 地 展 示 出 来 修 改 最 后 一 次 提 交 修 改 你 最 近 一 次 提 交 可 能 是 所 有 修 改 历 史 提 交 的 操 作 中 最 常 见 的 一 个 对 于 你 的 最 近 一 次 提 交, 你 往 往 想 做 两 件 事 情 : 修 改 提 交 信 息, 或 者 修 改 你 添 加 修 改 和 移 除 的 文 件 的 快 照 如 果, 你 只 是 想 修 改 最 近 一 次 提 交 的 提 交 信 息, 那 么 很 简 单 : $ git commit --amend 这 会 把 你 带 入 文 本 编 辑 器, 里 面 包 含 了 你 最 近 一 条 提 交 信 息, 供 你 修 改 当 保 存 并 关 闭 编 辑 器 后, 编 辑 器 将 会 用 你 输 入 的 内 容 替 换 最 近 一 条 提 交 信 息 如 果 你 已 经 完 成 提 交, 又 因 为 之 前 提 交 时 忘 记 添 加 一 个 新 创 建 的 文 件, 想 通 过 添 加 或 修 改 文 件 来 更 改 提 交 的 快 照, 也 可 以 通 过 类 似 的 操 作 来 完 成 通 过 修 改 文 件 然 后 运 行 git add 或 git rm 一 个 已 追 踪 的 文 件, 随 后 运 行 git commit --amend 拿 走 当 前 的 暂 存 区 域 并 使 其 做 为 新 提 交 的 快 照 使 用 这 个 技 巧 的 时 候 需 要 小 心, 因 为 修 正 会 改 变 提 交 的 SHA-1 校 验 和 它 类 似 于 一 个 小 的 变 基 - 如 果 已 经 推 送 了 最 后 一 次 提 交 就 不 要 修 正 它 修 改 多 个 提 交 信 息 为 了 修 改 在 提 交 历 史 中 较 远 的 提 交, 必 须 使 用 更 复 杂 的 工 具 Git 没 有 一 个 改 变 历 史 工 具, 但 是 可 以 使 用 变 基 工 具 来 变 基 一 系 列 提 交, 基 于 它 们 原 来 的 HEAD 而 不 是 将 其 移 动 到 另 一 个 新 的 上 面 通 过 交 互 式 变 基 工 具, 可 以 在 任 何 想 要 修 改 的 提 交 后 停 止, 然 后 修 改 信 息 添 加 文 件 或 做 任 何 想 做 的 事 情 可 以 通 过 给 git rebase 增 加 -i 选 项 来 交 互 式 地 运 行 变 基 必 须 指 定 想 要 重 写 多 久 远 的 历 史, 这 可 以 通 过 告 诉 命 令 将 要 变 基 到 的 提 交 来 做 到 例 如, 如 果 想 要 修 改 最 近 三 次 提 交 信 息, 或 者 那 组 提 交 中 的 任 意 一 个 提 交 信 息, 将 想 要 修 改 的 最 近 一 次 提 交 的 父 提 交 作 为 参 数 传 递 给 git rebase -i` 命 令, 即 `HEAD~2^ 或 HEAD~3 记 住 ~3 可 能 比 较 容 易, 因 为 你 正 尝 试 修 改 最 后 三 次 提 交 ; 但 是 注 意 实 际 上 指 定 了 以 前 的 四 次 提 交, 即 想 要 修 改 提 交 的 父 提 交 : $ git rebase -i HEAD~3 再 次 记 住 这 是 一 个 变 基 命 令 - 在 HEAD~3..HEAD 范 围 内 的 每 一 个 提 交 都 会 被 重 写, 无 论 你 是 否 修 改 信 息 不 要

237 涉 及 任 何 已 经 推 送 到 中 央 服 务 器 的 提 交 - 这 样 做 会 产 生 一 次 变 更 的 两 个 版 本, 因 而 使 他 人 困 惑 运 行 这 个 命 令 会 在 文 本 编 辑 器 上 给 你 一 个 提 交 的 列 表, 看 起 来 像 下 面 这 样 : pick f7f3f6d changed my name a bit pick e updated README formatting and added blame pick a5f4a0d added cat-file # Rebase 710f0f8..a5f4a0d onto 710f0f8 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out 需 要 重 点 注 意 的 是 相 对 于 正 常 使 用 的 log 命 令, 这 些 提 交 显 示 的 顺 序 是 相 反 的 运 行 一 次 log 命 令, 会 看 到 类 似 这 样 的 东 西 : $ git log --pretty=format:"%h %s" HEAD~3..HEAD a5f4a0d added cat-file e updated README formatting and added blame f7f3f6d changed my name a bit 注 意 其 中 的 反 序 显 示 交 互 式 变 基 给 你 一 个 它 将 会 运 行 的 脚 本 它 将 会 从 你 在 命 令 行 中 指 定 的 提 交 (HEAD~3) 开 始, 从 上 到 下 的 依 次 重 演 每 一 个 提 交 引 入 的 修 改 它 将 最 旧 的 而 不 是 最 新 的 列 在 上 面, 因 为 那 会 是 第 一 个 将 要 重 演 的 你 需 要 修 改 脚 本 来 让 它 停 留 在 你 想 修 改 的 变 更 上 要 达 到 这 个 目 的, 你 只 要 将 你 想 修 改 的 每 一 次 提 交 前 面 的 pick 改 为 edit 例 如, 只 想 修 改 第 三 次 提 交 信 息, 可 以 像 下 面 这 样 修 改 文 件 : edit f7f3f6d changed my name a bit pick e updated README formatting and added blame pick a5f4a0d added cat-file

238 当 保 存 并 退 出 编 辑 器 时,Git 将 你 带 回 到 列 表 中 的 最 后 一 次 提 交, 把 你 送 回 命 令 行 并 提 示 以 下 信 息 : $ git rebase -i HEAD~3 Stopped at f7f3f6d... changed my name a bit You can amend the commit now, with git commit --amend Once you re satisfied with your changes, run git rebase --continue 这 些 指 令 准 确 地 告 诉 你 该 做 什 么 输 入 $ git commit --amend 修 改 提 交 信 息, 然 后 退 出 编 辑 器 然 后, 运 行 $ git rebase --continue 这 个 命 令 将 会 自 动 地 应 用 另 外 两 个 提 交, 然 后 就 完 成 了 如 果 需 要 将 不 止 一 处 的 pick 改 为 edit, 需 要 在 每 一 个 修 改 为 edit 的 提 交 上 重 复 这 些 步 骤 每 一 次,Git 将 会 停 止, 让 你 修 正 提 交, 然 后 继 续 直 到 完 成 重 新 排 序 提 交 也 可 以 使 用 交 互 式 变 基 来 重 新 排 序 或 完 全 移 除 提 交 如 果 想 要 移 除 added cat-file 提 交 然 后 修 改 另 外 两 个 提 交 引 入 的 顺 序, 可 以 将 变 基 脚 本 从 这 样 : pick f7f3f6d changed my name a bit pick e updated README formatting and added blame pick a5f4a0d added cat-file 改 为 这 样 : pick e updated README formatting and added blame pick f7f3f6d changed my name a bit 当 保 存 并 退 出 编 辑 器 时,Git 将 你 的 分 支 带 回 这 些 提 交 的 父 提 交, 应 用 e 然 后 应 用 f7f3f6d, 最 后 停 止 事 实 修 改 了 那 些 提 交 的 顺 序 并 完 全 地 移 除 了 added cat-file 提 交

239 压 缩 提 交 通 过 交 互 式 变 基 工 具, 也 可 以 将 一 连 串 提 交 压 缩 成 一 个 单 独 的 提 交 在 变 基 信 息 中 脚 本 给 出 了 有 用 的 指 令 : # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out 如 果, 指 定 squash 而 不 是 pick 或 edit,git 将 应 用 两 者 的 修 改 并 合 并 提 交 信 息 在 一 起 所 以, 如 果 想 要 这 三 次 提 交 变 为 一 个 提 交, 可 以 这 样 修 改 脚 本 : pick f7f3f6d changed my name a bit squash e updated README formatting and added blame squash a5f4a0d added cat-file 当 保 存 并 退 出 编 辑 器 时,Git 应 用 所 有 的 三 次 修 改 然 后 将 你 放 到 编 辑 器 中 来 合 并 三 次 提 交 信 息 : # This is a combination of 3 commits. # The first commit's message is: changed my name a bit # This is the 2nd commit message: updated README formatting and added blame # This is the 3rd commit message: added cat-file 当 你 保 存 之 后, 你 就 拥 有 了 一 个 包 含 前 三 次 提 交 的 全 部 变 更 的 提 交

240 拆 分 提 交 拆 分 一 个 提 交 会 撤 消 这 个 提 交, 然 后 多 次 地 部 分 地 暂 存 与 提 交 直 到 完 成 你 所 需 次 数 的 提 交 例 如, 假 设 想 要 拆 分 三 次 提 交 的 中 间 那 次 提 交 想 要 将 它 拆 分 为 两 次 提 交 : 第 一 个 updated README formatting, 第 二 个 added blame 来 代 替 原 来 的 updated README formatting and added blame 可 以 通 过 修 改 rebase -i 的 脚 本 来 做 到 这 点, 将 要 拆 分 的 提 交 的 指 令 修 改 为 edit : pick f7f3f6d changed my name a bit edit e updated README formatting and added blame pick a5f4a0d added cat-file 然 后, 当 脚 本 将 你 进 入 到 命 令 行 时, 重 置 那 个 提 交, 拿 到 被 重 置 的 修 改, 从 中 创 建 几 次 提 交 当 保 存 并 退 出 编 辑 器 时,Git 带 你 到 列 表 中 第 一 个 提 交 的 父 提 交, 应 用 第 一 个 提 交 (f7f3f6d), 应 用 第 二 个 提 交 (310154e), 然 后 让 你 进 入 命 令 行 那 里, 可 以 通 过 git reset HEAD^ 做 一 次 针 对 那 个 提 交 的 混 合 重 置, 实 际 上 将 会 撤 消 那 次 提 交 并 将 修 改 的 文 件 未 暂 存 现 在 可 以 暂 存 并 提 交 文 件 直 到 有 几 个 提 交, 然 后 当 完 成 时 运 行 git rebase --continue: $ git reset HEAD^ $ git add README $ git commit -m 'updated README formatting' $ git add lib/simplegit.rb $ git commit -m 'added blame' $ git rebase --continue Git 在 脚 本 中 应 用 最 后 一 次 提 交 (a5f4a0d), 历 史 记 录 看 起 来 像 这 样 : $ git log -4 --pretty=format:"%h %s" 1c002dd added cat-file 9b29157 added blame 35cfb2b updated README formatting f3cc40e changed my name a bit 再 一 次, 这 些 改 动 了 所 有 在 列 表 中 的 提 交 的 SHA-1 校 验 和, 所 以 要 确 保 列 表 中 的 提 交 还 没 有 推 送 到 共 享 仓 库 中 核 武 器 级 选 项 :filter-branch 有 另 一 个 历 史 改 写 的 选 项, 如 果 想 要 通 过 脚 本 的 方 式 改 写 大 量 提 交 的 话 可 以 使 用 它 - 例 如, 全 局 修 改 你 的 邮 箱 地 址 或 从 每 一 个 提 交 中 移 除 一 个 文 件 这 个 命 令 是 filter-branch, 它 可 以 改 写 历 史 中 大 量 的 提 交, 除 非 你 的 项 目 还 没 有 公 开 并 且 其 他 人 没 有 基 于 要 改 写 的 工 作 的 提 交 做 的 工 作, 你 不 应 当 使 用 它 然 而, 它 可 以 很 有 用 你 将 会 学 习 到 几 个 常 用 的 用 途, 这 样 就 得 到 了 它 适 合 使 用 地 方 的 想 法

241 从 每 一 个 提 交 移 除 一 个 文 件 这 经 常 发 生 有 人 粗 心 地 通 过 git add. 提 交 了 一 个 巨 大 的 二 进 制 文 件, 你 想 要 从 所 有 地 方 删 除 它 可 能 偶 然 地 提 交 了 一 个 包 括 一 个 密 码 的 文 件, 然 而 你 想 要 开 源 项 目 filter-branch 是 一 个 可 能 会 用 来 擦 洗 整 个 提 交 历 史 的 工 具 为 了 从 整 个 提 交 历 史 中 移 除 一 个 叫 做 passwords.txt 的 文 件, 可 以 使 用 --tree-filter 选 项 给 filter-branch: $ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21) Ref 'refs/heads/master' was rewritten --tree-filter 选 项 在 检 出 项 目 的 每 一 个 提 交 后 运 行 指 定 的 命 令 然 后 重 新 提 交 结 果 在 本 例 中, 你 从 每 一 个 快 照 中 移 除 了 一 个 叫 作 passwords.txt 的 文 件, 无 论 它 是 否 存 在 如 果 想 要 移 除 所 有 偶 然 提 交 的 编 辑 器 备 份 文 件, 可 以 运 行 类 似 git filter-branch --tree-filter 'rm -f *~' HEAD 的 命 令 最 后 将 可 以 看 到 Git 重 写 树 与 提 交 然 后 移 动 分 支 指 针 通 常 一 个 好 的 想 法 是 在 一 个 测 试 分 支 中 做 这 件 事, 然 后 当 你 决 定 最 终 结 果 是 真 正 想 要 的, 可 以 硬 重 置 master 分 支 为 了 让 filter-branch 在 所 有 分 支 上 运 行, 可 以 给 命 令 传 递 --all 选 项 使 一 个 子 目 录 做 为 新 的 根 目 录 假 设 已 经 从 另 一 个 源 代 码 控 制 系 统 中 导 入, 并 且 有 几 个 没 意 义 的 子 目 录 (trunk tags 等 等 ) 如 果 想 要 让 trunk 子 目 录 作 为 每 一 个 提 交 的 新 的 项 目 根 目 录,filter-branch 也 可 以 帮 助 你 那 么 做 : $ git filter-branch --subdirectory-filter trunk HEAD Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12) Ref 'refs/heads/master' was rewritten 现 在 新 项 目 根 目 录 是 trunk 子 目 录 了 Git 会 自 动 移 除 所 有 不 影 响 子 目 录 的 提 交 全 局 修 改 邮 箱 地 址 另 一 个 常 见 的 情 形 是 在 你 开 始 工 作 时 忘 记 运 行 git config 来 设 置 你 的 名 字 与 邮 箱 地 址, 或 者 你 想 要 开 源 一 个 项 目 并 且 修 改 所 有 你 的 工 作 邮 箱 地 址 为 你 的 个 人 邮 箱 地 址 任 何 情 形 下, 你 也 可 以 通 过 filter-branch 来 一 次 性 修 改 多 个 提 交 中 的 邮 箱 地 址 需 要 小 心 的 是 只 修 改 你 自 己 的 邮 箱 地 址, 所 以 你 使 用 --commit-filter:

242 $ git filter-branch --commit-filter ' if [ "$GIT_AUTHOR_ " = "schacon@localhost" ]; then GIT_AUTHOR_NAME="Scott Chacon"; GIT_AUTHOR_ ="schacon@example.com"; git commit-tree "$@"; else git commit-tree "$@"; fi' HEAD 这 会 遍 历 并 重 写 每 一 个 提 交 来 包 含 你 的 新 邮 箱 地 址 因 为 提 交 包 含 了 它 们 父 提 交 的 SHA-1 校 验 和, 这 个 命 令 会 修 改 你 的 历 史 中 的 每 一 个 提 交 的 SHA-1 校 验 和, 而 不 仅 仅 只 是 那 些 匹 配 邮 箱 地 址 的 提 交 重 置 揭 密 在 继 续 了 解 更 专 业 的 工 具 前, 我 们 先 讨 论 一 下 reset 与 checkout 在 你 初 次 遇 到 的 Git 命 令 中, 这 两 个 是 最 让 人 困 惑 的 它 们 能 做 很 多 事 情, 所 以 看 起 来 我 们 很 难 真 正 地 理 解 并 恰 当 地 运 用 它 们 针 对 这 一 点, 我 们 先 来 做 一 个 简 单 的 比 喻 三 棵 树 理 解 reset 和 checkout 的 最 简 方 法, 就 是 以 Git 的 思 维 框 架 ( 将 其 作 为 内 容 管 理 器 ) 来 管 理 三 棵 不 同 的 树 树 在 我 们 这 里 的 实 际 意 思 是 文 件 的 集 合, 而 不 是 指 特 定 的 数 据 结 构 ( 在 某 些 情 况 下 索 引 看 起 来 并 不 像 一 棵 树, 不 过 我 们 现 在 的 目 的 是 用 简 单 的 方 式 思 考 它 ) Git 作 为 一 个 系 统, 是 以 它 的 一 般 操 作 来 管 理 并 操 纵 这 三 棵 树 的 : 树 HEAD Index Working Directory 用 途 上 一 次 提 交 的 快 照, 下 一 次 提 交 的 父 结 点 预 期 的 下 一 次 提 交 的 快 照 沙 盒 HEAD HEAD 是 当 前 分 支 引 用 的 指 针, 它 总 是 指 向 该 分 支 上 的 最 后 一 次 提 交 这 表 示 HEAD 将 是 下 一 次 提 交 的 父 结 点 通 常, 理 解 HEAD 的 最 简 方 式, 就 是 将 它 看 做 你 的 上 一 次 提 交 的 快 照 其 实, 查 看 快 照 的 样 子 很 容 易 下 例 就 显 示 了 HEAD 快 照 实 际 的 目 录 列 表, 以 及 其 中 每 个 文 件 的 SHA-1 校 验 和 :

243 $ git cat-file -p HEAD tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf author Scott Chacon committer Scott Chacon initial commit $ git ls-tree -r HEAD blob a906cb2a4a904a README blob 8f f9404f2... Rakefile tree 99f1a6d12cb4b6f19... lib cat-file 与 ls-tree 是 底 层 命 令, 它 们 一 般 用 于 底 层 工 作, 在 日 常 工 作 中 并 不 使 用 不 过 它 们 能 帮 助 我 们 了 解 到 底 发 生 了 什 么 索 引 索 引 是 你 的 预 期 的 下 一 次 提 交 我 们 也 会 将 这 个 概 念 引 用 为 Git 的 暂 存 区 域, 这 就 是 当 你 运 行 git commit 时 Git 看 起 来 的 样 子 Git 将 上 一 次 检 出 到 工 作 目 录 中 的 所 有 文 件 填 充 到 索 引 区, 它 们 看 起 来 就 像 最 初 被 检 出 时 的 样 子 之 后 你 会 将 其 中 一 些 文 件 替 换 为 新 版 本, 接 着 通 过 git commit 将 它 们 转 换 为 树 来 用 作 新 的 提 交 $ git ls-files -s a906cb2a4a904a152e80877d daad0c859 0 README f f9404f26296befa88755fc2598c289 0 Rakefile c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb 再 说 一 次, 我 们 在 这 里 又 用 到 了 ls-files 这 个 幕 后 的 命 令, 它 会 显 示 出 索 引 当 前 的 样 子 确 切 来 说, 索 引 并 非 技 术 上 的 树 结 构, 它 其 实 是 以 扁 平 的 清 单 实 现 的 不 过 对 我 们 而 言, 把 它 当 做 树 就 够 了 工 作 目 录 最 后, 你 就 有 了 自 己 的 工 作 目 录 另 外 两 棵 树 以 一 种 高 效 但 并 不 直 观 的 方 式, 将 它 们 的 内 容 存 储 在.git 文 件 夹 中 工 作 目 录 会 将 它 们 解 包 为 实 际 的 文 件 以 便 编 辑 你 可 以 把 工 作 目 录 当 做 沙 盒 在 你 将 修 改 提 交 到 暂 存 区 并 记 录 到 历 史 之 前, 可 以 随 意 更 改

244 $ tree. README Rakefile lib simplegit.rb 1 directory, 3 files 工 作 流 程 Git 主 要 的 目 的 是 通 过 操 纵 这 三 棵 树 来 以 更 加 连 续 的 状 态 记 录 项 目 的 快 照 让 我 们 来 可 视 化 这 个 过 程 : 假 设 我 们 进 入 到 一 个 新 目 录, 其 中 有 一 个 文 件 我 们 称 其 为 该 文 件 的 v1 版 本, 将 它 标 记 为 蓝 色 现 在 运 行 git init, 这 会 创 建 一 个 Git 仓 库, 其 中 的 HEAD 引 用 指 向 未 创 建 的 分 支 (master 还 不 存 在 )

245 此 时, 只 有 工 作 目 录 有 内 容 现 在 我 们 想 要 提 交 这 个 文 件, 所 以 用 git add 来 获 取 工 作 目 录 中 的 内 容, 并 将 其 复 制 到 索 引 中

246 接 着 运 行 git commit, 它 首 先 会 移 除 索 引 中 的 内 容 并 将 它 保 存 为 一 个 永 久 的 快 照, 然 后 创 建 一 个 指 向 该 快 照 的 提 交 对 象, 最 后 更 新 master 来 指 向 本 次 提 交

247 此 时 如 果 我 们 运 行 git status, 会 发 现 没 有 任 何 改 动, 因 为 现 在 三 棵 树 完 全 相 同 现 在 我 们 想 要 对 文 件 进 行 修 改 然 后 提 交 它 我 们 将 会 经 历 同 样 的 过 程 ; 首 先 在 工 作 目 录 中 修 改 文 件 我 们 称 其 为 该 文 件 的 v2 版 本, 并 将 它 标 记 为 红 色

248 如 果 现 在 运 行 git status, 我 们 会 看 到 文 件 显 示 在 Changes not staged for commit, 下 面 并 被 标 记 为 红 色, 因 为 该 条 目 在 索 引 与 工 作 目 录 之 间 存 在 不 同 接 着 我 们 运 行 git add 来 将 它 暂 存 到 索 引 中

249 此 时, 由 于 索 引 和 HEAD 不 同, 若 运 行 git status 的 话 就 会 看 到 Changes to be committed 下 的 该 文 件 变 为 绿 色 也 就 是 说, 现 在 预 期 的 下 一 次 提 交 与 上 一 次 提 交 不 同 最 后, 我 们 运 行 git commit 来 完 成 提 交

250 现 在 运 行 git status 会 没 有 输 出, 因 为 三 棵 树 又 变 得 相 同 了 切 换 分 支 或 克 隆 的 过 程 也 类 似 当 检 出 一 个 分 支 时, 它 会 修 改 HEAD 指 向 新 的 分 支 引 用, 将 索 引 填 充 为 该 次 提 交 的 快 照, 然 后 将 索 引 的 内 容 复 制 到 工 作 目 录 中 重 置 的 作 用 在 以 下 情 景 中 观 察 reset 命 令 会 更 有 意 义 为 了 演 示 这 些 例 子, 假 设 我 们 再 次 修 改 了 file.txt 文 件 并 第 三 次 提 交 它 现 在 的 历 史 看 起 来 是 这 样 的 :

251 让 我 们 跟 着 reset 看 看 它 都 做 了 什 么 它 以 一 种 简 单 可 预 见 的 方 式 直 接 操 纵 这 三 棵 树 它 做 了 三 个 基 本 操 作 第 1 步 : 移 动 HEAD reset 做 的 第 一 件 事 是 移 动 HEAD 的 指 向 这 与 改 变 HEAD 自 身 不 同 (checkout 所 做 的 );reset 移 动 HEAD 指 向 的 分 支 这 意 味 着 如 果 HEAD 设 置 为 master 分 支 ( 例 如, 你 正 在 master 分 支 上 ), 运 行 git reset 9e5e64a 将 会 使 master 指 向 9e5e64a

252 无 论 你 调 用 了 何 种 形 式 的 带 有 一 个 提 交 的 reset, 它 首 先 都 会 尝 试 这 样 做 使 用 reset --soft, 它 将 仅 仅 停 在 那 儿 现 在 看 一 眼 上 图, 理 解 一 下 发 生 的 事 情 : 它 本 质 上 是 撤 销 了 上 一 次 git commit 命 令 当 你 在 运 行 git commit 时,Git 会 创 建 一 个 新 的 提 交, 并 移 动 HEAD 所 指 向 的 分 支 来 使 其 指 向 该 提 交 当 你 将 它 reset 回 HEAD~(HEAD 的 父 结 点 ) 时, 其 实 就 是 把 该 分 支 移 动 回 原 来 的 位 置, 而 不 会 改 变 索 引 和 工 作 目 录 现 在 你 可 以 更 新 索 引 并 再 次 运 行 git commit 来 完 成 git commit --amend 所 要 做 的 事 情 了 ( 见 修 改 最 后 一 次 提 交 ) 第 2 步 : 更 新 索 引 (--mixed) 注 意, 如 果 你 现 在 运 行 git status 的 话, 就 会 看 到 新 的 HEAD 和 以 绿 色 标 出 的 它 和 索 引 之 间 的 区 别 接 下 来,reset 会 用 HEAD 指 向 的 当 前 快 照 的 内 容 来 更 新 索 引

253 如 果 指 定 --mixed 选 项,reset 将 会 在 这 时 停 止 这 也 是 默 认 行 为, 所 以 如 果 没 有 指 定 任 何 选 项 ( 在 本 例 中 只 是 git reset HEAD~), 这 就 是 命 令 将 会 停 止 的 地 方 现 在 再 看 一 眼 上 图, 理 解 一 下 发 生 的 事 情 : 它 依 然 会 撤 销 一 上 次 提 交, 但 还 会 取 消 暂 存 所 有 的 东 西 于 是, 我 们 回 滚 到 了 所 有 git add 和 git commit 的 命 令 执 行 之 前 第 3 步 : 更 新 工 作 目 录 (--hard) reset 要 做 的 的 第 三 件 事 情 就 是 让 工 作 目 录 看 起 来 像 索 引 如 果 使 用 --hard 选 项, 它 将 会 继 续 这 一 步

254 现 在 让 我 们 回 想 一 下 刚 才 发 生 的 事 情 你 撤 销 了 最 后 的 提 交 git add 和 git commit 命 令 以 及 工 作 目 录 中 的 所 有 工 作 必 须 注 意,--hard 标 记 是 reset 命 令 唯 一 的 危 险 用 法, 它 也 是 Git 会 真 正 地 销 毁 数 据 的 仅 有 的 几 个 操 作 之 一 其 他 任 何 形 式 的 reset 调 用 都 可 以 轻 松 撤 消, 但 是 --hard 选 项 不 能, 因 为 它 强 制 覆 盖 了 工 作 目 录 中 的 文 件 在 这 种 特 殊 情 况 下, 我 们 的 Git 数 据 库 中 的 一 个 提 交 内 还 留 有 该 文 件 的 v3 版 本, 我 们 可 以 通 过 reflog 来 找 回 它 但 是 若 该 文 件 还 未 提 交,Git 仍 会 覆 盖 它 从 而 导 致 无 法 恢 复 回 顾 reset 命 令 会 以 特 定 的 顺 序 重 写 这 三 棵 树, 在 你 指 定 以 下 选 项 时 停 止 : 1. 移 动 HEAD 分 支 的 指 向 ( 若 指 定 了 --soft, 则 到 此 停 止 ) 2. 使 索 引 看 起 来 像 HEAD ( 若 未 指 定 --hard, 则 到 此 停 止 )

255 3. 使 工 作 目 录 看 起 来 像 索 引 通 过 路 径 来 重 置 前 面 讲 述 了 reset 基 本 形 式 的 行 为, 不 过 你 还 可 以 给 它 提 供 一 个 作 用 路 径 若 指 定 了 一 个 路 径,reset 将 会 跳 过 第 1 步, 并 且 将 它 的 作 用 范 围 限 定 为 指 定 的 文 件 或 文 件 集 合 这 样 做 自 然 有 它 的 道 理, 因 为 HEAD 只 是 一 个 指 针, 你 无 法 让 它 同 时 指 向 两 个 提 交 中 各 自 的 一 部 分 不 过 索 引 和 工 作 目 录 可 以 部 分 更 新, 所 以 重 置 会 继 续 进 行 第 2 3 步 现 在, 假 如 我 们 运 行 git reset file.txt( 这 其 实 是 git reset --mixed HEAD file.txt 的 简 写 形 式, 因 为 你 既 没 有 指 定 一 个 提 交 的 SHA-1 或 分 支, 也 没 有 指 定 --soft 或 --hard), 它 会 : 1. 移 动 HEAD 分 支 的 指 向 ( 已 跳 过 ) 2. 让 索 引 看 起 来 像 HEAD ( 到 此 处 停 止 ) 所 以 它 本 质 上 只 是 将 file.txt 从 HEAD 复 制 到 索 引 中

256 它 还 有 取 消 暂 存 文 件 的 实 际 效 果 如 果 我 们 查 看 该 命 令 的 示 意 图, 然 后 再 想 想 git add 所 做 的 事, 就 会 发 现 它 们 正 好 相 反

257 这 就 是 为 什 么 git status 命 令 的 输 出 会 建 议 运 行 此 命 令 来 取 消 暂 存 一 个 文 件 ( 查 看 取 消 暂 存 的 文 件 来 了 解 更 多 ) 我 们 可 以 不 让 Git 从 HEAD 拉 取 数 据, 而 是 通 过 具 体 指 定 一 个 提 交 来 拉 取 该 文 件 的 对 应 版 本 我 们 只 需 运 行 类 似 于 git reset eb43bf file.txt 的 命 令 即 可

258 它 其 实 做 了 同 样 的 事 情, 也 就 是 把 工 作 目 录 中 的 文 件 恢 复 到 v1 版 本, 运 行 git add 添 加 它, 然 后 再 将 它 恢 复 到 v3 版 本 ( 只 是 不 用 真 的 过 一 遍 这 些 步 骤 ) 如 果 我 们 现 在 运 行 git commit, 它 就 会 记 录 一 条 将 该 文 件 恢 复 到 v1 版 本 的 更 改, 尽 管 我 们 并 未 在 工 作 目 录 中 真 正 地 再 次 拥 有 它 还 有 一 点 同 git add 一 样, 就 是 reset 命 令 也 可 以 接 受 一 个 --patch 选 项 来 一 块 一 块 地 取 消 暂 存 的 内 容 这 样 你 就 可 以 根 据 选 择 来 取 消 暂 存 或 恢 复 内 容 了 压 缩 我 们 来 看 看 如 何 利 用 这 种 新 的 功 能 来 做 一 些 有 趣 的 事 情 - 压 缩 提 交 假 设 你 的 一 系 列 提 交 信 息 中 有 oops. WIP 和 forgot this file, 聪 明 的 你 就 能 使 用 reset 来 轻 松 快 速 地 将 它 们 压 缩 成 单 个 提 交, 也 显 出 你 的 聪 明 ( 压 缩 提 交 展 示 了 另 一 种 方 式, 不 过 在 本 例 中 用 reset 更 简 单 ) 假 设 你 有 一 个 项 目, 第 一 次 提 交 中 有 一 个 文 件, 第 二 次 提 交 增 加 了 一 个 新 的 文 件 并 修 改 了 第 一 个 文 件, 第 三 次 提

259 交 再 次 修 改 了 第 一 个 文 件 由 于 第 二 次 提 交 是 一 个 未 完 成 的 工 作, 因 此 你 想 要 压 缩 它 那 么 可 以 运 行 git reset --soft HEAD~2 来 将 HEAD 分 支 移 动 到 一 个 旧 一 点 的 提 交 上 ( 即 你 想 要 保 留 的 第 一 个 提 交 ):

260 然 后 只 需 再 次 运 行 git commit:

261 现 在 你 可 以 查 看 可 到 达 的 历 史, 即 将 会 推 送 的 历 史, 现 在 看 起 来 有 个 v1 版 file-a.txt 的 提 交, 接 着 第 二 个 提 交 将 file-a.txt 修 改 成 了 v3 版 并 增 加 了 file-b.txt 包 含 v2 版 本 的 文 件 已 经 不 在 历 史 中 了 检 出 最 后, 你 大 概 还 想 知 道 checkout 和 reset 之 间 的 区 别 和 reset 一 样,checkout 也 操 纵 三 棵 树, 不 过 它 有 一 点 不 同, 这 取 决 于 你 是 否 传 给 该 命 令 一 个 文 件 路 径

262 不 带 路 径 运 行 git checkout [branch] 与 运 行 git reset --hard [branch] 非 常 相 似, 它 会 更 新 所 有 三 棵 树 使 其 看 起 来 像 [branch], 不 过 有 两 点 重 要 的 区 别 首 先 不 同 于 reset --hard,checkout 对 工 作 目 录 是 安 全 的, 它 会 通 过 检 查 来 确 保 不 会 将 已 更 改 的 文 件 吹 走 其 实 它 还 更 聪 明 一 些 它 会 在 工 作 目 录 中 先 试 着 简 单 合 并 一 下, 这 样 所 有 _ 还 未 修 改 过 的 _ 文 件 都 会 被 更 新 而 reset --hard 则 会 不 做 检 查 就 全 面 地 替 换 所 有 东 西 第 二 个 重 要 的 区 别 是 如 何 更 新 HEAD reset 会 移 动 HEAD 分 支 的 指 向, 而 checkout 只 会 移 动 HEAD 自 身 来 指 向 另 一 个 分 支 例 如, 假 设 我 们 有 master 和 develop 分 支, 它 们 分 别 指 向 不 同 的 提 交 ; 我 们 现 在 在 develop 上 ( 所 以 HEAD 指 向 它 ) 如 果 我 们 运 行 git reset master, 那 么 develop 自 身 现 在 会 和 master 指 向 同 一 个 提 交 而 如 果 我 们 运 行 git checkout master 的 话,develop 不 会 移 动,HEAD 自 身 会 移 动 现 在 HEAD 将 会 指 向 master 所 以, 虽 然 在 这 两 种 情 况 下 我 们 都 移 动 HEAD 使 其 指 向 了 提 交 A, 但 _ 做 法 _ 是 非 常 不 同 的 reset 会 移 动 HEAD 分 支 的 指 向, 而 checkout 则 移 动 HEAD 自 身

263 带 路 径 运 行 checkout 的 另 一 种 方 式 就 是 指 定 一 个 文 件 路 径, 这 会 像 reset 一 样 不 会 移 动 HEAD 它 就 像 git reset [branch] file 那 样 用 该 次 提 交 中 的 那 个 文 件 来 更 新 索 引, 但 是 它 也 会 覆 盖 工 作 目 录 中 对 应 的 文 件 它 就 像 是 git reset --hard [branch] file( 如 果 reset 允 许 你 这 样 运 行 的 话 )- 这 样 对 工 作 目 录 并 不 安 全, 它 也 不 会 移 动 HEAD 此 外, 同 git reset 和 git add 一 样,checkout 也 接 受 一 个 --patch 选 项, 允 许 你 根 据 选 择 一 块 一 块 地 恢 复 文 件 内 容 总 结 希 望 你 现 在 熟 悉 并 理 解 了 reset 命 令, 不 过 关 于 它 和 checkout 之 间 的 区 别, 你 可 能 还 是 会 有 点 困 惑, 毕 竟 不 太 可 能 记 住 不 同 调 用 的 所 有 规 则 下 面 的 速 查 表 列 出 了 命 令 对 树 的 影 响 HEAD 一 列 中 的 REF 表 示 该 命 令 移 动 了 HEAD 指 向 的 分 支 引 用, 而 ` HEAD ' 则 表 示 只 移 动 了 HEAD 自 身 特 别 注 意 WD Safe? 一 列 - 如 果 它 标 记 为 NO, 那 么 运 行 该 命 令 之 前 请 考 虑 一 下 HEAD Index Workdir WD Safe? Commit Level reset --soft [commit] REF NO NO YES reset [commit] REF YES NO YES reset --hard [commit] REF YES YES NO checkout [commit] HEAD YES YES YES File Level reset (commit) [file] NO YES NO YES checkout (commit) [file] NO YES YES NO 高 级 合 并 在 Git 中 合 并 是 相 当 容 易 的 因 为 Git 使 多 次 合 并 另 一 个 分 支 变 得 很 容 易, 这 意 味 着 你 可 以 有 一 个 始 终 保 持 最 新 的 长 期 分 支, 经 常 解 决 小 的 冲 突, 比 在 一 系 列 提 交 后 解 决 一 个 巨 大 的 冲 突 要 好 然 而, 有 时 也 会 有 棘 手 的 冲 突 不 像 其 他 的 版 本 控 制 系 统,Git 并 不 会 尝 试 过 于 聪 明 的 合 并 冲 突 解 决 方 案 Git 的 哲 学 是 聪 明 地 决 定 无 歧 义 的 合 并 方 案, 但 是 如 果 有 冲 突, 它 不 会 尝 试 智 能 地 自 动 解 决 它 因 此, 如 果 很 久 之 后 才 合 并 两 个 分 叉 的 分 支, 你 可 能 会 撞 上 一 些 问 题 在 本 节 中, 我 们 将 会 仔 细 查 看 那 些 问 题 是 什 么 以 及 Git 给 了 我 们 什 么 工 具 来 帮 助 我 们 处 理 这 些 更 难 办 的 情 形 我 们 也 会 了 解 你 可 以 做 的 不 同 的 非 标 准 类 型 的 合 并, 也 会 看 到 如 何 后 退 到 合 并 之 前

264 合 并 冲 突 我 们 在 遇 到 冲 突 时 的 分 支 合 并 介 绍 了 解 决 合 并 冲 突 的 一 些 基 础 知 识, 对 于 更 复 杂 的 冲 突,Git 提 供 了 几 个 工 具 来 帮 助 你 指 出 将 会 发 生 什 么 以 及 如 何 更 好 地 处 理 冲 突 首 先, 在 做 一 次 可 能 有 冲 突 的 合 并 前 尽 可 能 保 证 工 作 目 录 是 干 净 的 如 果 你 有 正 在 做 的 工 作, 要 么 提 交 到 一 个 临 时 分 支 要 么 储 藏 它 这 使 你 可 以 撤 消 在 这 里 尝 试 做 的 * 任 何 事 情 * 如 果 在 你 尝 试 一 次 合 并 时 工 作 目 录 中 有 未 保 存 的 改 动, 下 面 的 这 些 技 巧 可 能 会 使 你 丢 失 那 些 工 作 让 我 们 通 过 一 个 非 常 简 单 的 例 子 来 了 解 一 下 我 们 有 一 个 超 级 简 单 的 打 印 hello world 的 Ruby 文 件 #! /usr/bin/env ruby def hello puts 'hello world' end hello() 在 我 们 的 仓 库 中, 创 建 一 个 名 为 whitespace 的 新 分 支 并 将 所 有 Unix 换 行 符 修 改 为 DOS 换 行 符, 实 质 上 虽 然 改 变 了 文 件 的 每 一 行, 但 改 变 的 都 只 是 空 白 字 符 然 后 我 们 修 改 行 hello world 为 hello mundo

265 $ git checkout -b whitespace Switched to a new branch 'whitespace' $ unix2dos hello.rb unix2dos: converting file hello.rb to DOS format... $ git commit -am 'converted hello.rb to DOS' [whitespace 3270f76] converted hello.rb to DOS 1 file changed, 7 insertions(+), 7 deletions(-) $ vim hello.rb $ git diff -b diff --git a/hello.rb b/hello.rb index ac51efd..e85207e a/hello.rb ,7 #! /usr/bin/env ruby def hello - puts 'hello world' + puts 'hello mundo'^m end hello() $ git commit -am 'hello mundo change' [whitespace 6d338d2] hello mundo change 1 file changed, 1 insertion(+), 1 deletion(-) 现 在 我 们 切 换 回 我 们 的 master 分 支 并 为 函 数 增 加 一 些 注 释

266 $ git checkout master Switched to branch 'master' $ vim hello.rb $ git diff diff --git a/hello.rb b/hello.rb index ac51efd..36c06c a/hello.rb ,5 #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello world' end $ git commit -am 'document the function' [master bec6336] document the function 1 file changed, 1 insertion(+) 现 在 我 们 尝 试 合 并 入 我 们 的 whitespace 分 支, 因 为 修 改 了 空 白 字 符, 所 以 合 并 会 出 现 冲 突 $ git merge whitespace Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result. 中 断 一 次 合 并 我 们 现 在 有 几 个 选 项 首 先, 让 我 们 介 绍 如 何 摆 脱 这 个 情 况 你 可 能 不 想 处 理 冲 突 这 种 情 况, 完 全 可 以 通 过 git merge --abort 来 简 单 地 退 出 合 并 $ git status -sb ## master UU hello.rb $ git merge --abort $ git status -sb ## master git merge --abort 选 项 会 尝 试 恢 复 到 你 运 行 合 并 前 的 状 态 但 当 运 行 命 令 前, 在 工 作 目 录 中 有 未 储 藏 未 提 交 的 修 改 时 它 不 能 完 美 处 理, 除 此 之 外 它 都 工 作 地 很 好

267 如 果 因 为 某 些 原 因 你 发 现 自 己 处 在 一 个 混 乱 的 状 态 中 然 后 只 是 想 要 重 来 一 次, 也 可 以 运 行 git reset --hard HEAD 回 到 之 前 的 状 态 或 其 他 你 想 要 恢 复 的 状 态 请 牢 记 这 会 将 清 除 工 作 目 录 中 的 所 有 内 容, 所 以 确 保 你 不 需 要 保 存 这 里 的 任 意 改 动 忽 略 空 白 在 这 个 特 定 的 例 子 中, 冲 突 与 空 白 有 关 我 们 知 道 这 点 是 因 为 这 个 例 子 很 简 单, 但 是 在 实 际 的 例 子 中 发 现 这 样 的 冲 突 也 很 容 易, 因 为 每 一 行 都 被 移 除 而 在 另 一 边 每 一 行 又 被 加 回 来 了 默 认 情 况 下,Git 认 为 所 有 这 些 行 都 改 动 了, 所 以 它 不 会 合 并 文 件 默 认 合 并 策 略 可 以 带 有 参 数, 其 中 的 几 个 正 好 是 关 于 忽 略 空 白 改 动 的 如 果 你 看 到 在 一 次 合 并 中 有 大 量 的 空 白 问 题, 你 可 以 简 单 地 中 止 它 并 重 做 一 次, 这 次 使 用 -Xignore-all-space 或 -Xignore-space-change 选 项 第 一 个 选 项 忽 略 任 意 数 量 的 已 有 空 白 的 修 改, 第 二 个 选 项 忽 略 所 有 空 白 修 改 $ git merge -Xignore-space-change whitespace Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb file changed, 1 insertion(+), 1 deletion(-) 因 为 在 本 例 中, 实 际 上 文 件 修 改 并 没 有 冲 突, 一 旦 我 们 忽 略 空 白 修 改, 每 一 行 都 能 被 很 好 地 合 并 如 果 你 的 团 队 中 的 某 个 人 可 能 不 小 心 重 新 格 式 化 空 格 为 制 表 符 或 者 相 反 的 操 作, 这 会 是 一 个 救 命 稻 草 手 动 文 件 再 合 并 虽 然 Git 对 空 白 的 预 处 理 做 得 很 好, 还 有 很 多 其 他 类 型 的 修 改,Git 也 许 无 法 自 动 处 理, 但 是 脚 本 可 以 处 理 它 们 例 如, 假 设 Git 无 法 处 理 空 白 修 改 因 此 我 们 需 要 手 动 处 理 我 们 真 正 想 要 做 的 是 对 将 要 合 并 入 的 文 件 在 真 正 合 并 前 运 行 dos2unix 程 序 所 以 如 果 那 样 的 话, 我 们 该 如 何 做? 首 先, 我 们 进 入 到 了 合 并 冲 突 状 态 然 后 我 们 想 要 我 的 版 本 的 文 件, 他 们 的 版 本 的 文 件 ( 从 我 们 将 要 合 并 入 的 分 支 ) 和 共 同 的 版 本 的 文 件 ( 从 分 支 叉 开 时 的 位 置 ) 的 拷 贝 然 后 我 们 想 要 修 复 任 何 一 边 的 文 件, 并 且 为 这 个 单 独 的 文 件 重 试 一 次 合 并 获 得 这 三 个 文 件 版 本 实 际 上 相 当 容 易 Git 在 索 引 中 存 储 了 所 有 这 些 版 本, 在 stages 下 每 一 个 都 有 一 个 数 字 与 它 们 关 联 Stage 1 是 它 们 共 同 的 祖 先 版 本,stage 2 是 你 的 版 本,stage 3 来 自 于 MERGE_HEAD, 即 你 将 要 合 并 入 的 版 本 ( theirs ) 通 过 git show 命 令 与 一 个 特 别 的 语 法, 你 可 以 将 冲 突 文 件 的 这 些 版 本 释 放 出 一 份 拷 贝

268 $ git show :1:hello.rb > hello.common.rb $ git show :2:hello.rb > hello.ours.rb $ git show :3:hello.rb > hello.theirs.rb 如 果 你 想 要 更 专 业 一 点, 也 可 以 使 用 ls-files -u 底 层 命 令 来 得 到 这 些 文 件 的 Git blob 对 象 的 实 际 SHA-1 值 $ git ls-files -u ac51efdc3df4f4fd328d1a02ad05331d8e2c hello.rb c06c8752c78d2aff f3bf7841a7b5c3 2 hello.rb e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb :1:hello.rb 只 是 查 找 那 个 blob 对 象 SHA-1 值 的 简 写 既 然 在 我 们 的 工 作 目 录 中 已 经 有 这 所 有 三 个 阶 段 的 内 容, 我 们 可 以 手 工 修 复 它 们 来 修 复 空 白 问 题, 然 后 使 用 鲜 为 人 知 的 git merge-file 命 令 来 重 新 合 并 那 个 文 件 $ dos2unix hello.theirs.rb dos2unix: converting file hello.theirs.rb to Unix format... $ git merge-file -p \ hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb $ git diff -b diff --cc hello.rb index 36c06c8,e85207e a/hello.rb ,8-1,7 #! /usr/bin/env ruby +# prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello() 在 这 时 我 们 已 经 漂 亮 地 合 并 了 那 个 文 件 实 际 上, 这 比 使 用 ignore-space-change 选 项 要 更 好, 因 为 在 合 并 前 真 正 地 修 复 了 空 白 修 改 而 不 是 简 单 地 忽 略 它 们 在 使 用 ignore-space-change 进 行 合 并 操 作 后, 我 们 最 终 得 到 了 有 几 行 是 DOS 行 尾 的 文 件, 从 而 使 提 交 内 容 混 乱 了

269 如 果 你 想 要 在 最 终 提 交 前 看 一 下 我 们 这 边 与 另 一 边 之 间 实 际 的 修 改, 你 可 以 使 用 git diff 来 比 较 将 要 提 交 作 为 合 并 结 果 的 工 作 目 录 与 其 中 任 意 一 个 阶 段 的 文 件 差 异 让 我 们 看 看 它 们 要 在 合 并 前 比 较 结 果 与 在 你 的 分 支 上 的 内 容, 换 一 句 话 说, 看 看 合 并 引 入 了 什 么, 可 以 运 行 git diff --ours $ git diff --ours * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index 36c06c8..44d0a a/hello.rb ,7 # prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello() 这 里 我 们 可 以 很 容 易 地 看 到 在 我 们 的 分 支 上 发 生 了 什 么, 在 这 次 合 并 中 我 们 实 际 引 入 到 这 个 文 件 的 改 动, 是 修 改 了 其 中 一 行 如 果 我 们 想 要 查 看 合 并 的 结 果 与 他 们 那 边 有 什 么 不 同, 可 以 运 行 git diff --theirs 在 本 例 及 后 续 的 例 子 中, 我 们 会 使 用 -b 来 去 除 空 白, 因 为 我 们 将 它 与 Git 中 的, 而 不 是 我 们 清 理 过 的 hello.theirs.rb 文 件 比 较 $ git diff --theirs -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index e85207e..44d0a a/hello.rb ,5 #! /usr/bin/env ruby +# prints out a greeting def hello puts 'hello mundo' end 最 终, 你 可 以 通 过 git diff --base 来 查 看 文 件 在 两 边 是 如 何 改 动 的

270 $ git diff --base -b * Unmerged path hello.rb diff --git a/hello.rb b/hello.rb index ac51efd..44d0a a/hello.rb ,7 #! /usr/bin/env ruby +# prints out a greeting def hello - puts 'hello world' + puts 'hello mundo' end hello() 在 这 时 我 们 可 以 使 用 git clean 命 令 来 清 理 我 们 为 手 动 合 并 而 创 建 但 不 再 有 用 的 额 外 文 件 $ git clean -f Removing hello.common.rb Removing hello.ours.rb Removing hello.theirs.rb 检 出 冲 突 也 许 有 时 我 们 并 不 满 意 这 样 的 解 决 方 案, 或 许 有 时 还 要 手 动 编 辑 一 边 或 者 两 边 的 冲 突, 但 还 是 依 旧 无 法 正 常 工 作, 这 时 我 们 需 要 更 多 的 上 下 文 关 联 来 解 决 这 些 冲 突 让 我 们 来 稍 微 改 动 下 例 子 对 于 本 例, 我 们 有 两 个 长 期 分 支, 每 一 个 分 支 都 有 几 个 提 交, 但 是 在 合 并 时 却 创 建 了 一 个 合 理 的 冲 突 $ git log --graph --oneline --decorate --all * f1270f7 (HEAD, master) update README * 9af9d3b add a README * d update phrase to hola world * e3eb223 (mundo) add more tests * 7cff591 add testing script * c3ffff1 changed text to hello mundo / * b7dcc89 initial hello world code 现 在 有 只 在 master 分 支 上 的 三 次 单 独 提 交, 还 有 其 他 三 次 提 交 在 mundo 分 支 上 如 果 我 们 尝 试 将 mundo 分 支 合 并 入 master 分 支, 我 们 得 到 一 个 冲 突

271 $ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Automatic merge failed; fix conflicts and then commit the result. 我 们 想 要 看 一 下 合 并 冲 突 是 什 么 如 果 我 们 打 开 这 个 文 件, 我 们 将 会 看 到 类 似 下 面 的 内 容 : #! /usr/bin/env ruby def hello <<<<<<< HEAD puts 'hola world' ======= puts 'hello mundo' >>>>>>> mundo end hello() 合 并 的 两 边 都 向 这 个 文 件 增 加 了 内 容, 但 是 导 致 冲 突 的 原 因 是 其 中 一 些 提 交 修 改 了 文 件 的 同 一 个 地 方 让 我 们 探 索 一 下 现 在 你 手 边 可 用 来 查 明 这 个 冲 突 是 如 何 产 生 的 工 具 应 该 如 何 修 复 这 个 冲 突 看 起 来 或 许 并 不 明 显 这 时 你 需 要 更 多 上 下 文 一 个 很 有 用 的 工 具 是 带 --conflict 选 项 的 git checkout 这 会 重 新 检 出 文 件 并 替 换 合 并 冲 突 标 记 如 果 想 要 重 置 标 记 并 尝 试 再 次 解 决 它 们 的 话 这 会 很 有 用 可 以 传 递 给 --conflict 参 数 diff3 或 merge( 默 认 选 项 ) 如 果 传 给 它 diff3,git 会 使 用 一 个 略 微 不 同 版 本 的 冲 突 标 记 : 不 仅 仅 只 给 你 ours 和 theirs 版 本, 同 时 也 会 有 base 版 本 在 中 间 来 给 你 更 多 的 上 下 文 $ git checkout --conflict=diff3 hello.rb 一 旦 我 们 运 行 它, 文 件 看 起 来 会 像 下 面 这 样 :

272 #! /usr/bin/env ruby def hello <<<<<<< ours puts 'hola world' base puts 'hello world' ======= puts 'hello mundo' >>>>>>> theirs end hello() 如 果 你 喜 欢 这 种 格 式, 可 以 通 过 设 置 merge.conflictstyle 选 项 为 diff3 来 做 为 以 后 合 并 冲 突 的 默 认 选 项 $ git config --global merge.conflictstyle diff3 git checkout 命 令 也 可 以 使 用 --ours 和 --theirs 选 项, 这 是 一 种 无 需 合 并 的 快 速 方 式, 你 可 以 选 择 留 下 一 边 的 修 改 而 丢 弃 掉 另 一 边 修 改 当 有 二 进 制 文 件 冲 突 时 这 可 能 会 特 别 有 用, 因 为 可 以 简 单 地 选 择 一 边, 或 者 可 以 只 合 并 另 一 个 分 支 的 特 定 文 件 - 可 以 做 一 次 合 并 然 后 在 提 交 前 检 出 一 边 或 另 一 边 的 特 定 文 件 合 并 日 志 另 一 个 解 决 合 并 冲 突 有 用 的 工 具 是 git log 这 可 以 帮 助 你 得 到 那 些 对 冲 突 有 影 响 的 上 下 文 回 顾 一 点 历 史 来 记 起 为 什 么 两 条 线 上 的 开 发 会 触 碰 同 一 片 代 码 有 时 会 很 有 用 为 了 得 到 此 次 合 并 中 包 含 的 每 一 个 分 支 的 所 有 独 立 提 交 的 列 表, 我 们 可 以 使 用 之 前 在 三 点 学 习 的 三 点 语 法 $ git log --oneline --left-right HEAD...MERGE_HEAD < f1270f7 update README < 9af9d3b add a README < d update phrase to hola world > e3eb223 add more tests > 7cff591 add testing script > c3ffff1 changed text to hello mundo 这 个 漂 亮 的 列 表 包 含 6 个 提 交 和 每 一 个 提 交 所 在 的 不 同 开 发 路 径

273 我 们 可 以 通 过 更 加 特 定 的 上 下 文 来 进 一 步 简 化 这 个 列 表 如 果 我 们 添 加 --merge 选 项 到 git log 中, 它 会 只 显 示 任 何 一 边 接 触 了 合 并 冲 突 文 件 的 提 交 $ git log --oneline --left-right --merge < d update phrase to hola world > c3ffff1 changed text to hello mundo 如 果 你 运 行 命 令 时 用 -p 选 项 代 替, 你 会 得 到 所 有 冲 突 文 件 的 区 别 快 速 获 得 你 需 要 帮 助 理 解 为 什 么 发 生 冲 突 的 上 下 文, 以 及 如 何 聪 明 地 解 决 它, 这 会 非 常 有 用 组 合 式 差 异 格 式 因 为 Git 暂 存 合 并 成 功 的 结 果, 当 你 在 合 并 冲 突 状 态 下 运 行 git diff 时, 只 会 得 到 现 在 还 在 冲 突 状 态 的 区 别 当 需 要 查 看 你 还 需 要 解 决 哪 些 冲 突 时 这 很 有 用 在 合 并 冲 突 后 直 接 运 行 的 git diff 会 给 你 一 个 相 当 独 特 的 输 出 格 式 $ git diff diff --cc hello.rb index 0399cd5,59727f a/hello.rb ,7-1,7 #! /usr/bin/env ruby def hello ++<<<<<<< HEAD + puts 'hola world' ++======= + puts 'hello mundo' ++>>>>>>> mundo end hello() 这 种 叫 作 组 合 式 差 异 的 格 式 会 在 每 一 行 给 你 两 列 数 据 第 一 列 为 你 显 示 ours 分 支 与 工 作 目 录 的 文 件 区 别 ( 添 加 或 删 除 ), 第 二 列 显 示 theirs 分 支 与 工 作 目 录 的 拷 贝 区 别 所 以 在 上 面 的 例 子 中 可 以 看 到 <<<<<<< 与 >>>>>>> 行 在 工 作 拷 贝 中 但 是 并 不 在 合 并 的 任 意 一 边 中 这 很 有 意 义, 合 并 工 具 因 为 我 们 的 上 下 文 被 困 住 了, 它 期 望 我 们 去 移 除 它 们 如 果 我 们 解 决 冲 突 再 次 运 行 git diff, 我 们 将 会 看 到 同 样 的 事 情, 但 是 它 有 一 点 帮 助

274 $ vim hello.rb $ git diff diff --cc hello.rb index 0399cd5,59727f a/hello.rb ,7-1,7 #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello() 这 里 显 示 出 hola world 在 我 们 这 边 但 不 在 工 作 拷 贝 中, 那 个 hello mundo 在 他 们 那 边 但 不 在 工 作 拷 贝 中, 最 终 hola mundo 不 在 任 何 一 边 但 是 现 在 在 工 作 拷 贝 中 在 提 交 解 决 方 案 前 这 对 审 核 很 有 用 也 可 以 在 合 并 后 通 过 git log 来 获 取 相 同 信 息, 并 查 看 冲 突 是 如 何 解 决 的 如 果 你 对 一 个 合 并 提 交 运 行 git show 命 令 Git 将 会 输 出 这 种 格 式, 或 者 你 也 可 以 在 git log -p( 默 认 情 况 下 该 命 令 只 会 展 示 还 没 有 合 并 的 补 丁 ) 命 令 之 后 加 上 --cc 选 项

275 $ git log --cc -p -1 commit 14f d80b9e17bb c33f8d5b5a79 Merge: f1270f7 e3eb223 Author: Scott Chacon Date: Fri Sep 19 18:14: Merge branch 'mundo' Conflicts: hello.rb diff --cc hello.rb index 0399cd5,59727f0..e1d a/hello.rb +++ b/hello.rb -1,7-1,7 +1,7 #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end hello() 撤 消 合 并 虽 然 你 已 经 知 道 如 何 创 建 一 个 合 并 提 交, 但 有 时 出 错 是 在 所 难 免 的 使 用 Git 最 棒 的 一 件 事 情 是 犯 错 是 可 以 的, 因 为 有 可 能 ( 大 多 数 情 况 下 都 很 容 易 ) 修 复 它 们 合 并 提 交 并 无 不 同 假 设 现 在 在 一 个 特 性 分 支 上 工 作, 不 小 心 将 其 合 并 到 master 中, 现 在 提 交 历 史 看 起 来 是 这 样 :

276 Figure 138. 意 外 的 合 并 提 交 有 两 种 方 法 来 解 决 这 个 问 题, 这 取 决 于 你 想 要 的 结 果 是 什 么 修 复 引 用 如 果 这 个 不 想 要 的 合 并 提 交 只 存 在 于 你 的 本 地 仓 库 中, 最 简 单 且 最 好 的 解 决 方 案 是 移 动 分 支 到 你 想 要 它 指 向 的 地 方 大 多 数 情 况 下, 如 果 你 在 错 误 的 git merge 后 运 行 git reset --hard HEAD~, 这 会 重 置 分 支 指 向 所 以 它 们 看 起 来 像 这 样 : Figure 139. 在 git reset --hard HEAD~ 之 后 的 历 史 我 们 之 前 在 重 置 揭 密 已 经 介 绍 了 reset, 所 以 现 在 指 出 这 里 发 生 了 什 么 并 不 是 很 困 难 让 我 们 快 速 复 习 下 :reset --hard 通 常 会 经 历 三 步 :

277 1. 移 动 HEAD 指 向 的 分 支 在 本 例 中, 我 们 想 要 移 动 master 到 合 并 提 交 (C6) 之 前 所 在 的 位 置 2. 使 索 引 看 起 来 像 HEAD 3. 使 工 作 目 录 看 起 来 像 索 引 这 个 方 法 的 缺 点 是 它 会 重 写 历 史, 在 一 个 共 享 的 仓 库 中 这 会 造 成 问 题 的 查 阅 变 基 的 风 险 来 了 解 更 多 可 能 发 生 的 事 情 ; 用 简 单 的 话 说 就 是 如 果 其 他 人 已 经 有 你 将 要 重 写 的 提 交, 你 应 当 避 免 使 用 reset 如 果 有 任 何 其 他 提 交 在 合 并 之 后 创 建 了, 那 么 这 个 方 法 也 会 无 效 ; 移 动 引 用 实 际 上 会 丢 失 那 些 改 动 还 原 提 交 如 果 移 动 分 支 指 针 并 不 适 合 你,Git 给 你 一 个 生 成 一 个 新 提 交 的 选 项, 提 交 将 会 撤 消 一 个 已 存 在 提 交 的 所 有 修 改 Git 称 这 个 操 作 为 还 原, 在 这 个 特 定 的 场 景 下, 你 可 以 像 这 样 调 用 它 : $ git revert -m 1 HEAD [master b1d8379] Revert "Merge branch 'topic'" -m 1 标 记 指 出 mainline 需 要 被 保 留 下 来 的 父 结 点 当 你 引 入 一 个 合 并 到 HEAD(git merge topic), 新 提 交 有 两 个 父 结 点 : 第 一 个 是 HEAD(C6), 第 二 个 是 将 要 合 并 入 分 支 的 最 新 提 交 (C4) 在 本 例 中, 我 们 想 要 撤 消 所 有 由 父 结 点 #2(C4) 合 并 引 入 的 修 改, 同 时 保 留 从 父 结 点 #1(C4) 开 始 的 所 有 内 容 有 还 原 提 交 的 历 史 看 起 来 像 这 样 : Figure 140. 在 git revert -m 1 后 的 历 史 新 的 提 交 ^M 与 C6 有 完 全 一 样 的 内 容, 所 以 从 这 儿 开 始 就 像 合 并 从 未 发 生 过, 除 了 现 在 还 没 合 并 的 提 交 依 然 在 HEAD 的 历 史 中 如 果 你 尝 试 再 次 合 并 topic 到 master Git 会 感 到 困 惑 : $ git merge topic Already up-to-date.

278 topic 中 并 没 有 东 西 不 能 从 master 中 追 踪 到 达 更 糟 的 是, 如 果 你 在 topic 中 增 加 工 作 然 后 再 次 合 并,Git 只 会 引 入 被 还 原 的 合 并 之 后 的 修 改 Figure 141. 含 有 坏 掉 合 并 的 历 史 解 决 这 个 最 好 的 方 式 是 撤 消 还 原 原 始 的 合 并, 因 为 现 在 你 想 要 引 入 被 还 原 出 去 的 修 改, 然 后 创 建 一 个 新 的 合 并 提 交 : $ git revert ^M [master 09f0126] Revert "Revert "Merge branch 'topic'"" $ git merge topic Figure 142. 在 重 新 合 并 一 个 还 原 合 并 后 的 历 史 在 本 例 中,M 与 ^M 抵 消 了 ^^M 事 实 上 合 并 入 了 C3 与 C4 的 修 改,C8 合 并 了 C7 的 修 改, 所 以 现 在 topic 已 经 完 全 被 合 并 了 其 他 类 型 的 合 并 到 目 前 为 止 我 们 介 绍 的 都 是 通 过 一 个 叫 作 recursive 的 合 并 策 略 来 正 常 处 理 的 两 个 分 支 的 正 常 合 并 然 而 还 有 其 他 方 式 来 合 并 两 个 分 支 到 一 起 让 我 们 来 快 速 介 绍 其 中 的 几 个

279 我 们 的 或 他 们 的 偏 好 首 先, 有 另 一 种 我 们 可 以 通 过 recursive 合 并 模 式 做 的 有 用 工 作 我 们 之 前 已 经 看 到 传 递 给 -X 的 ignoreall-space 与 ignore-space-change 选 项, 但 是 我 们 也 可 以 告 诉 Git 当 它 看 见 一 个 冲 突 时 直 接 选 择 一 边 默 认 情 况 下, 当 Git 看 到 两 个 分 支 合 并 中 的 冲 突 时, 它 会 将 合 并 冲 突 标 记 添 加 到 你 的 代 码 中 并 标 记 文 件 为 冲 突 状 态 来 让 你 解 决 如 果 你 希 望 Git 简 单 地 选 择 特 定 的 一 边 并 忽 略 另 外 一 边 而 不 是 让 你 手 动 合 并 冲 突, 你 可 以 传 递 给 merge 命 令 一 个 -Xours 或 -Xtheirs 参 数 如 果 Git 看 到 这 个, 它 并 不 会 增 加 冲 突 标 记 任 何 可 以 合 并 的 区 别, 它 会 直 接 合 并 任 何 有 冲 突 的 区 别, 它 会 简 单 地 选 择 你 全 局 指 定 的 一 边, 包 括 二 进 制 文 件 如 果 我 们 回 到 之 前 我 们 使 用 的 hello world 例 子 中, 我 们 可 以 看 到 合 并 入 我 们 的 分 支 时 引 发 了 冲 突 $ git merge mundo Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Resolved 'hello.rb' using previous resolution. Automatic merge failed; fix conflicts and then commit the result. 然 而 如 果 我 们 运 行 时 增 加 -Xours 或 -Xtheirs 参 数 就 不 会 有 冲 突 $ git merge -Xours mundo Auto-merging hello.rb Merge made by the 'recursive' strategy. hello.rb 2 +- test.sh files changed, 3 insertions(+), 1 deletion(-) create mode test.sh 在 上 例 中, 它 并 不 会 为 hello mundo 与 hola world 标 记 合 并 冲 突, 它 只 会 简 单 地 选 取 hola world 然 而, 在 那 个 分 支 上 所 有 其 他 非 冲 突 的 改 动 都 可 以 被 成 功 地 合 并 入 这 个 选 项 也 可 以 传 递 给 我 们 之 前 看 到 的 git merge-file 命 令, 通 过 运 行 类 似 git merge-file --ours 的 命 令 来 合 并 单 个 文 件 如 果 想 要 做 类 似 的 事 情 但 是 甚 至 并 不 想 让 Git 尝 试 合 并 另 外 一 边 的 修 改, 有 一 个 更 严 格 的 选 项, 它 是 ours 合 并 策 略 这 与 ours recursive 合 并 选 项 不 同 这 本 质 上 会 做 一 次 假 的 合 并 它 会 记 录 一 个 以 两 边 分 支 作 为 父 结 点 的 新 合 并 提 交, 但 是 它 甚 至 根 本 不 关 注 你 正 合 并 入 的 分 支 它 只 会 简 单 地 把 当 前 分 支 的 代 码 当 作 合 并 结 果 记 录 下 来

280 $ git merge -s ours mundo Merge made by the 'ours' strategy. $ git diff HEAD HEAD~ $ 你 可 以 看 到 合 并 后 与 合 并 前 我 们 的 分 支 并 没 有 任 何 区 别 当 再 次 合 并 时 从 本 质 上 欺 骗 Git 认 为 那 个 分 支 已 经 合 并 过 经 常 是 很 有 用 的 例 如, 假 设 你 有 一 个 分 叉 的 release 分 支 并 且 在 上 面 做 了 一 些 你 想 要 在 未 来 某 个 时 候 合 并 回 master 的 工 作 与 此 同 时 master 分 支 上 的 某 些 bugfix 需 要 向 后 移 植 回 release 分 支 你 可 以 合 并 bugfix 分 支 进 入 release 分 支 同 时 也 merge -s ours 合 并 进 入 你 的 master 分 支 ( 即 使 那 个 修 复 已 经 在 那 儿 了 ) 这 样 当 你 之 后 再 次 合 并 release 分 支 时, 就 不 会 有 来 自 bugfix 的 冲 突 子 树 合 并 子 树 合 并 的 思 想 是 你 有 两 个 项 目, 并 且 其 中 一 个 映 射 到 另 一 个 项 目 的 一 个 子 目 录, 或 者 反 过 来 也 行 当 你 执 行 一 个 子 树 合 并 时,Git 通 常 可 以 自 动 计 算 出 其 中 一 个 是 另 外 一 个 的 子 树 从 而 实 现 正 确 的 合 并 我 们 来 看 一 个 例 子 如 何 将 一 个 项 目 加 入 到 一 个 已 存 在 的 项 目 中, 然 后 将 第 二 个 项 目 的 代 码 合 并 到 第 一 个 项 目 的 子 目 录 中 首 先, 我 们 将 Rack 应 用 添 加 到 你 的 项 目 里 我 们 把 Rack 项 目 作 为 一 个 远 程 的 引 用 添 加 到 我 们 的 项 目 里, 然 后 检 出 到 它 自 己 的 分 支 $ git remote add rack_remote $ git fetch rack_remote warning: no common commits remote: Counting objects: 3184, done. remote: Compressing objects: 100% (1465/1465), done. remote: Total 3184 (delta 1952), reused 2770 (delta 1675) Receiving objects: 100% (3184/3184), KiB 4 KiB/s, done. Resolving deltas: 100% (1952/1952), done. From * [new branch] build -> rack_remote/build * [new branch] master -> rack_remote/master * [new branch] rack-0.4 -> rack_remote/rack-0.4 * [new branch] rack-0.9 -> rack_remote/rack-0.9 $ git checkout -b rack_branch rack_remote/master Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master. Switched to a new branch "rack_branch" 现 在 在 我 们 的 rack_branch 分 支 里 就 有 Rack 项 目 的 根 目 录, 而 我 们 的 项 目 则 在 master 分 支 里 如 果 你 从 一 个 分 支 切 换 到 另 一 个 分 支, 你 可 以 看 到 它 们 的 项 目 根 目 录 是 不 同 的 :

281 $ ls AUTHORS KNOWN-ISSUES Rakefile contrib lib COPYING README bin example test $ git checkout master Switched to branch "master" $ ls README 这 个 是 一 个 比 较 奇 怪 的 概 念 并 不 是 仓 库 中 的 所 有 分 支 都 是 必 须 属 于 同 一 个 项 目 的 分 支. 这 并 不 常 见, 因 为 没 啥 用, 但 是 却 是 在 不 同 分 支 里 包 含 两 条 完 全 不 同 提 交 历 史 的 最 简 单 的 方 法 在 这 个 例 子 中, 我 们 希 望 将 Rack 项 目 拉 到 master 项 目 中 作 为 一 个 子 目 录 我 们 可 以 在 Git 中 执 行 git read-tree 来 实 现 你 可 以 在 Git 内 部 原 理 中 查 看 更 多 read-tree 的 相 关 信 息, 现 在 你 只 需 要 知 道 它 会 读 取 一 个 分 支 的 根 目 录 树 到 当 前 的 暂 存 区 和 工 作 目 录 里 先 切 回 你 的 master 分 支, 将 rack_back 分 支 拉 取 到 我 们 项 目 的 master 分 支 中 的 rack 子 目 录 $ git read-tree --prefix=rack/ -u rack_branch 当 我 们 提 交 时, 那 个 子 目 录 中 拥 有 所 有 Rack 项 目 的 文 件 就 像 我 们 直 接 从 压 缩 包 里 复 制 出 来 的 一 样 有 趣 的 是 你 可 以 很 容 易 地 将 一 个 分 支 的 变 更 合 并 到 另 一 个 分 支 里 所 以, 当 Rack 项 目 有 更 新 时, 我 们 可 以 切 换 到 那 个 分 支 来 拉 取 上 游 的 变 更 $ git checkout rack_branch $ git pull 接 着, 我 们 可 以 将 这 些 变 更 合 并 回 我 们 的 master 分 支 使 用 --squash 选 项 和 使 用 -Xsubtree 选 项 ( 它 采 用 递 归 合 并 策 略 ), 都 可 以 用 来 可 以 拉 取 变 更 并 且 预 填 充 提 交 信 息 ( 递 归 策 略 在 这 里 是 默 认 的, 提 到 它 是 为 了 让 读 者 有 个 清 晰 的 概 念 ) $ git checkout master $ git merge --squash -s recursive -Xsubtree=rack rack_branch Squash commit -- not updating HEAD Automatic merge went well; stopped before committing as requested Rack 项 目 中 所 有 的 改 动 都 被 合 并 了, 等 待 被 提 交 到 本 地 你 也 可 以 用 相 反 的 方 法 在 master 分 支 上 的 rack 子 目 录 中 做 改 动 然 后 将 它 们 合 并 入 你 的 rack_branch 分 支 中, 之 后 你 可 能 将 其 提 交 给 项 目 维 护 着 或 者 将 它 们 推 送 到 上 游 这 给 我 们 提 供 了 一 种 类 似 子 模 块 工 作 流 的 工 作 方 式, 但 是 它 并 不 需 要 用 到 子 模 块 ( 有 关 子 模 块 的 内 容 我 们 会 在 子 模 块 中 介 绍 ) 我 们 可 以 在 自 己 的 仓 库 中 保 持 一 些 和 其 他 项 目 相 关 的 分 支, 偶 尔 使 用 子 树 合 并 将 它 们 合 并 到 我 们 的 项 目 中 某 些 时 候 这 种 方 式 很 有 用, 例 如 当 所 有 的 代 码 都 提 交 到 一 个 地 方 的 时 候 然 而, 它 同 时 也 有 缺

282 点, 它 更 加 复 杂 且 更 容 易 让 人 犯 错, 例 如 重 复 合 并 改 动 或 者 不 小 心 将 分 支 提 交 到 一 个 无 关 的 仓 库 上 去 另 外 一 个 有 点 奇 怪 的 地 方 是, 当 你 想 查 看 rack 子 目 录 和 rack_branch 分 支 的 差 异 来 确 定 你 是 否 需 要 合 并 它 们 你 不 能 使 用 普 通 的 diff 命 令 取 而 代 之 的 是, 你 必 须 使 用 git diff-tree 来 和 你 的 目 标 分 支 做 比 较 : $ git diff-tree -p rack_branch 或 者, 将 你 的 rack 子 目 和 最 近 一 次 从 服 务 器 上 抓 取 的 master 分 支 进 行 比 较, 你 可 以 运 行 : $ git diff-tree -p rack_remote/master Rerere git rerere 功 能 是 一 个 隐 藏 的 功 能 正 如 它 的 名 字 reuse recorded resolution 所 指, 它 允 许 你 让 Git 记 住 解 决 一 个 块 冲 突 的 方 法, 这 样 在 下 一 次 看 到 相 同 冲 突 时,Git 可 以 为 你 自 动 地 解 决 它 有 几 种 情 形 下 这 个 功 能 会 非 常 有 用 在 文 档 中 提 到 的 一 个 例 子 是 如 果 你 想 要 保 证 一 个 长 期 分 支 会 干 净 地 合 并, 但 是 又 不 想 要 一 串 中 间 的 合 并 提 交 将 rerere 功 能 打 开 后 偶 尔 合 并, 解 决 冲 突, 然 后 返 回 到 合 并 前 如 果 你 持 续 这 样 做, 那 么 最 终 的 合 并 会 很 容 易, 因 为 rerere 可 以 为 你 自 动 做 所 有 的 事 情 可 以 将 同 样 的 策 略 用 在 维 持 一 个 变 基 的 分 支 时, 这 样 就 不 用 每 次 解 决 同 样 的 变 基 冲 突 了 或 者 你 将 一 个 分 支 合 并 并 修 复 了 一 堆 冲 突 后 想 要 用 变 基 来 替 代 合 并 - 你 可 能 并 不 想 要 再 次 解 决 相 同 的 冲 突 另 一 个 情 形 是 当 你 偶 尔 将 一 堆 正 在 改 进 的 特 性 分 支 合 并 到 一 个 可 测 试 的 头 时, 就 像 Git 项 目 自 身 经 常 做 的 如 果 测 试 失 败, 你 可 以 倒 回 合 并 之 前 然 后 在 去 除 导 致 测 试 失 败 的 那 个 特 性 分 支 后 重 做 合 并, 而 不 用 再 次 重 新 解 决 所 有 的 冲 突 为 了 启 用 rerere 功 能, 仅 仅 需 要 运 行 这 个 配 置 选 项 : $ git config --global rerere.enabled true 也 通 过 在 特 定 的 仓 库 中 创 建.git/rr-cache 目 录 来 开 启 它, 但 是 设 置 选 项 更 干 净 并 且 可 以 应 用 到 全 局 现 在 我 们 看 一 个 简 单 的 例 子, 类 似 之 前 的 那 个 假 设 有 一 个 像 这 样 的 文 件 :

283 #! /usr/bin/env ruby def hello puts 'hello world' end 在 一 个 分 支 中 修 改 单 词 hello 为 hola, 然 后 在 另 一 个 分 支 中 修 改 world 为 mundo, 就 像 之 前 一 样 当 合 并 两 个 分 支 到 一 起 时, 我 们 将 会 得 到 一 个 合 并 冲 突 : $ git merge i18n-world Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Recorded preimage for 'hello.rb' Automatic merge failed; fix conflicts and then commit the result. 你 会 注 意 到 那 个 新 行 Recorded preimage for FILE 除 此 之 外 它 应 该 看 起 来 就 像 一 个 普 通 的 合 并 冲 突 在 这 个 时 候,rerere 可 以 告 诉 我 们 几 件 事 和 往 常 一 样, 在 这 个 时 候 你 可 以 运 行 git status 来 查 看 所 有 冲 突 的 内 容 :

284 $ git status # On branch master # Unmerged paths: # (use "git reset HEAD <file>..." to unstage) # (use "git add <file>..." to mark resolution) # # both modified: hello.rb # 然 而,git rerere 也 会 通 过 git rerere status 告 诉 你 它 记 录 的 合 并 前 状 态 $ git rerere status hello.rb 并 且 git rerere diff 将 会 显 示 解 决 方 案 的 当 前 状 态 - 开 始 解 决 前 与 解 决 后 的 样 子 $ git rerere diff --- a/hello.rb ,11 #! /usr/bin/env ruby def hello -<<<<<<< - puts 'hello mundo' -======= +<<<<<<< HEAD puts 'hola world' ->>>>>>> +======= + puts 'hello mundo' +>>>>>>> i18n-world end 同 样 ( 这 并 不 是 真 的 与 rerere 有 关 系 ), 可 以 使 用 ls-files -u 来 查 看 冲 突 文 件 的 之 前 左 边 与 右 边 版 本 : $ git ls-files -u c942a9c1f2c03dc7c5ebcd7f3e3a6b hello.rb a440db6e8d1fd76ad438a49025a9ad9ce746f581 2 hello.rb ba847c3758ab e hello.rb 现 在 可 以 通 过 改 为 puts 'hola mundo' 来 解 决 它, 可 以 再 次 运 行 rerere diff 命 令 来 查 看 rerere 将 会 记 住

285 的 内 容 : $ git rerere diff --- a/hello.rb ,11 #! /usr/bin/env ruby def hello -<<<<<<< - puts 'hello mundo' -======= - puts 'hola world' ->>>>>>> + puts 'hola mundo' end 所 以 从 本 质 上 说, 当 Git 看 到 一 个 hello.rb 文 件 的 一 个 块 冲 突 中 有 hello mundo 在 一 边 与 hola world 在 另 一 边, 它 会 将 其 解 决 为 hola mundo 现 在 我 们 可 以 将 它 标 记 为 已 解 决 并 提 交 它 : $ git add hello.rb $ git commit Recorded resolution for 'hello.rb'. [master 68e16e5] Merge branch 'i18n' 可 以 看 到 它 "Recorded resolution for FILE"

286 现 在, 让 我 们 撤 消 那 个 合 并 然 后 将 它 变 基 到 master 分 支 顶 部 来 替 代 它 可 以 通 过 使 用 之 前 在 重 置 揭 密 看 到 的 reset 来 回 滚 分 支 $ git reset --hard HEAD^ HEAD is now at ad63f15 i18n the hello 我 们 的 合 并 被 撤 消 了 现 在 让 我 们 变 基 特 性 分 支 $ git checkout i18n-world Switched to branch 'i18n-world' $ git rebase master First, rewinding head to replay your work on top of it... Applying: i18n one word Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... Auto-merging hello.rb CONFLICT (content): Merge conflict in hello.rb Resolved 'hello.rb' using previous resolution. Failed to merge in the changes. Patch failed at 0001 i18n one word 现 在, 正 像 我 们 期 望 的 一 样, 得 到 了 相 同 的 合 并 冲 突, 但 是 看 一 下 Resolved FILE using previous resolution 这 行 如 果 我 们 看 这 个 文 件, 会 发 现 它 已 经 被 解 决 了, 而 且 在 它 里 面 没 有 合 并 冲 突 标 记

287 $ cat hello.rb #! /usr/bin/env ruby def hello puts 'hola mundo' end 同 样,git diff 将 会 显 示 出 它 是 如 何 自 动 地 重 新 解 决 的 : $ git diff diff --cc hello.rb index a440db6,54336ba a/hello.rb ,7-1,7 #! /usr/bin/env ruby def hello - puts 'hola world' - puts 'hello mundo' ++ puts 'hola mundo' end 也 可 以 通 过 checkout 命 令 重 新 恢 复 到 冲 突 时 候 的 文 件 状 态 :

288 $ git checkout --conflict=merge hello.rb $ cat hello.rb #! /usr/bin/env ruby def hello <<<<<<< ours puts 'hola world' ======= puts 'hello mundo' >>>>>>> theirs end 我 们 将 会 在 高 级 合 并 中 看 到 这 个 的 一 个 例 子 然 而 现 在, 让 我 们 通 过 运 行 rerere 来 重 新 解 决 它 : $ git rerere Resolved 'hello.rb' using previous resolution. $ cat hello.rb #! /usr/bin/env ruby def hello puts 'hola mundo' end 我 们 通 过 rerere 缓 存 的 解 决 方 案 来 自 动 重 新 解 决 了 文 件 冲 突 现 在 可 以 添 加 并 继 续 变 基 来 完 成 它 $ git add hello.rb $ git rebase --continue Applying: i18n one word 所 以, 如 果 做 了 很 多 次 重 新 合 并, 或 者 想 要 一 个 特 性 分 支 始 终 与 你 的 master 分 支 保 持 最 新 但 却 不 想 要 一 大 堆 合 并, 或 者 经 常 变 基, 打 开 rerere 功 能 可 以 帮 助 你 的 生 活 变 得 更 美 好 使 用 Git 调 试 Git 也 提 供 了 两 个 工 具 来 辅 助 你 调 试 项 目 中 的 问 题 由 于 Git 被 设 计 成 适 用 于 几 乎 所 有 类 型 的 项 目, 这 些 工 具 是 比 较 通 用 的, 但 它 们 可 以 在 出 现 问 题 的 时 候 帮 助 你 找 到 bug 或 者 错 误 文 件 标 注 如 果 你 在 追 踪 代 码 中 的 一 个 bug, 并 且 想 知 道 是 什 么 时 候 以 及 为 何 会 引 入, 文 件 标 注 通 常 是 最 好 用 的 工 具 它 展 示 了 文 件 中 每 一 行 最 后 一 次 修 改 的 提 交 所 以, 如 果 你 在 代 码 中 看 到 一 个 有 问 题 的 方 法, 你 可 以 使 用 git blame 标 注 这 个 文 件, 查 看 这 个 方 法 每 一 行 的 最 后 修 改 时 间 以 及 是 被 谁 修 改 的 这 个 例 子 使 用 -L 选 项 来 限 制 输

289 出 范 围 在 第 12 至 22 行 : $ git blame -L 12,22 simplegit.rb ^4832fe2 (Scott Chacon :31: ) def show(tree = 'master') ^4832fe2 (Scott Chacon :31: ) command("git show #{tree}") ^4832fe2 (Scott Chacon :31: ) end ^4832fe2 (Scott Chacon :31: ) 9f6560e4 (Scott Chacon :52: ) def log(tree = 'master') 79eaf55d (Scott Chacon :15: ) command("git log #{tree}") 9f6560e4 (Scott Chacon :52: ) end 9f6560e4 (Scott Chacon :52: ) 42cf2861 (Magnus Chacon :45: ) def blame(path) 42cf2861 (Magnus Chacon :45: ) command("git blame #{path}") 42cf2861 (Magnus Chacon :45: ) end 请 注 意, 第 一 个 字 段 是 最 后 一 次 修 改 该 行 的 提 交 的 部 分 SHA-1 值 接 下 来 两 个 字 段 的 值 是 从 提 交 中 提 取 出 来 的 作 者 的 名 字 以 及 提 交 的 时 间 所 以 你 就 可 以 很 轻 易 地 找 到 是 谁 在 什 么 时 候 修 改 了 那 一 行 接 下 来 就 是 行 号 和 文 件 内 容 注 意 一 下 ^4832fe2 这 个 提 交 的 那 些 行, 这 些 指 的 是 这 个 文 件 第 一 次 提 交 的 那 些 行 这 个 提 交 是 这 个 文 件 第 一 次 加 入 到 这 个 项 目 时 的 提 交, 并 且 这 些 行 从 未 被 修 改 过 这 会 带 来 小 小 的 困 惑, 因 为 你 已 经 至 少 看 到 三 种 Git 使 用 ^ 来 修 饰 一 个 提 交 的 SHA-1 值 的 不 同 含 义, 但 这 里 确 实 就 是 这 个 意 思 另 一 件 比 较 酷 的 事 情 是 Git 不 会 显 式 地 记 录 文 件 的 重 命 名 它 会 记 录 快 照, 然 后 在 事 后 尝 试 计 算 出 重 命 名 的 动 作 这 其 中 有 一 个 很 有 意 思 的 特 性 就 是 你 可 以 让 Git 找 出 所 有 的 代 码 移 动 如 果 你 在 git blame 后 面 加 上 一 个 -C,Git 会 分 析 你 正 在 标 注 的 文 件, 并 且 尝 试 找 出 文 件 中 从 别 的 地 方 复 制 过 来 的 代 码 片 段 的 原 始 出 处 比 如, 你 将 GITServerHandler.m 这 个 文 件 拆 分 为 数 个 文 件, 其 中 一 个 文 件 是 GITPackUpload.m 对 GITPackUpload.m 执 行 带 -C 参 数 的 blame 命 令, 你 就 可 以 看 到 代 码 块 的 原 始 出 处 :

290 $ git blame -C -L 141,153 GITPackUpload.m f344f58d GITServerHandler.m (Scott ) f344f58d GITServerHandler.m (Scott ) - (void) gatherobjectshasfromc f344f58d GITServerHandler.m (Scott ) { 70befddd GITServerHandler.m (Scott ) //NSLog(@"GATHER COMMI ad11ac80 GITPackUpload.m (Scott ) ad11ac80 GITPackUpload.m (Scott ) NSString *parentsha; ad11ac80 GITPackUpload.m (Scott ) GITCommit *commit = [g ad11ac80 GITPackUpload.m (Scott ) ad11ac80 GITPackUpload.m (Scott ) //NSLog(@"GATHER COMMI ad11ac80 GITPackUpload.m (Scott ) 56ef2caf GITServerHandler.m (Scott ) if(commit) { 56ef2caf GITServerHandler.m (Scott ) [refdict setob 56ef2caf GITServerHandler.m (Scott ) 这 个 功 能 很 有 用 通 常 来 说, 你 会 认 为 复 制 代 码 过 来 的 那 个 提 交 是 最 原 始 的 提 交, 因 为 那 是 你 第 一 次 在 这 个 文 件 中 修 改 了 这 几 行 但 Git 会 告 诉 你, 你 第 一 次 写 这 几 行 代 码 的 那 个 提 交 才 是 原 始 提 交, 即 使 这 是 在 另 外 一 个 文 件 里 写 的 二 分 查 找 当 你 知 道 问 题 是 在 哪 里 引 入 的 情 况 下 文 件 标 注 可 以 帮 助 你 查 找 问 题 如 果 你 不 知 道 哪 里 出 了 问 题, 并 且 自 从 上 次 可 以 正 常 运 行 到 现 在 已 经 有 数 十 个 或 者 上 百 个 提 交, 这 个 时 候 你 可 以 使 用 git bisect 来 帮 助 查 找 bisect 命 令 会 对 你 的 提 交 历 史 进 行 二 分 查 找 来 帮 助 你 尽 快 找 到 是 哪 一 个 提 交 引 入 了 问 题 假 设 你 刚 刚 在 线 上 环 境 部 署 了 你 的 代 码, 接 着 收 到 一 些 bug 反 馈, 但 这 些 bug 在 你 之 前 的 开 发 环 境 里 没 有 出 现 过, 这 让 你 百 思 不 得 其 解 你 重 新 查 看 了 你 的 代 码, 发 现 这 个 问 题 是 可 以 被 重 现 的, 但 是 你 不 知 道 哪 里 出 了 问 题 你 可 以 用 二 分 法 来 找 到 这 个 问 题 首 先 执 行 git bisect start 来 启 动, 接 着 执 行 git bisect bad 来 告 诉 系 统 当 前 你 所 在 的 提 交 是 有 问 题 的 然 后 你 必 须 告 诉 bisect 已 知 的 最 后 一 次 正 常 状 态 是 哪 次 提 交, 使 用 git bisect good [good_commit]: $ git bisect start $ git bisect bad $ git bisect good v1.0 Bisecting: 6 revisions left to test after this [ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] error handling on repo Git 发 现 在 你 标 记 为 正 常 的 提 交 (v1.0) 和 当 前 的 错 误 版 本 之 间 有 大 约 12 次 提 交, 于 是 Git 检 出 中 间 的 那 个 提 交 现 在 你 可 以 执 行 测 试, 看 看 在 这 个 提 交 下 问 题 是 不 是 还 是 存 在 如 果 还 存 在, 说 明 问 题 是 在 这 个 提 交 之 前 引 入 的 ;

291 如 果 问 题 不 存 在, 说 明 问 题 是 在 这 个 提 交 之 后 引 入 的 假 设 测 试 结 果 是 没 有 问 题 的, 你 可 以 通 过 git bisect good 来 告 诉 Git, 然 后 继 续 寻 找 $ git bisect good Bisecting: 3 revisions left to test after this [b047b02ea83310a70fd603dc8cd7a6cd13d15c04] secure this thing 现 在 你 在 另 一 个 提 交 上 了, 这 个 提 交 是 刚 刚 那 个 测 试 通 过 的 提 交 和 有 问 题 的 提 交 的 中 点 你 再 一 次 执 行 测 试, 发 现 这 个 提 交 下 是 有 问 题 的, 因 此 你 可 以 通 过 git bisect bad 告 诉 Git: $ git bisect bad Bisecting: 1 revisions left to test after this [f71ce38690acf49c1f3c9bea38e09d82a5ce6014] drop exceptions table 这 个 提 交 是 正 常 的, 现 在 Git 拥 有 的 信 息 已 经 可 以 确 定 引 入 问 题 的 位 置 在 哪 里 它 会 告 诉 你 第 一 个 错 误 提 交 的 SHA-1 值 并 显 示 一 些 提 交 说 明, 以 及 哪 些 文 件 在 那 次 提 交 里 修 改 过, 这 样 你 可 以 找 出 引 入 bug 的 根 源 : $ git bisect good b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04 Author: PJ Hyett <pjhyett@example.com> Date: Tue Jan 27 14:48: secure this thing : ee3e7821b895e52c db9bdc4c61d1730 f24d3c6ebcfc639b1a e62d60b8e68a8e4 M config 当 你 完 成 这 些 操 作 之 后, 你 应 该 执 行 git bisect reset 重 置 你 的 HEAD 指 针 到 最 开 始 的 位 置, 否 则 你 会 停 留 在 一 个 很 奇 怪 的 状 态 : $ git bisect reset 这 是 一 个 可 以 帮 助 你 在 几 分 钟 内 从 数 百 个 提 交 中 找 到 bug 的 强 大 工 具 事 实 上, 如 果 你 有 一 个 脚 本 在 项 目 是 正 常 的 情 况 下 返 回 0, 在 不 正 常 的 情 况 下 返 回 非 0, 你 可 以 使 git bisect 自 动 化 这 些 操 作 首 先, 你 设 定 好 项 目 正 常 以 及 不 正 常 所 在 提 交 的 二 分 查 找 范 围 你 可 以 通 过 bisect start 命 令 的 参 数 来 设 定 这 两 个 提 交, 第 一 个 参 数 是 项 目 不 正 常 的 提 交, 第 二 个 参 数 是 项 目 正 常 的 提 交 : $ git bisect start HEAD v1.0 $ git bisect run test-error.sh

292 Git 会 自 动 在 每 个 被 检 出 的 提 交 里 执 行 test-error.sh 直 到 找 到 第 一 个 项 目 不 正 常 的 提 交 你 也 可 以 执 行 make 或 者 make tests 或 者 其 他 东 西 来 进 行 自 动 化 测 试 子 模 块 有 种 情 况 我 们 经 常 会 遇 到 : 某 个 工 作 中 的 项 目 需 要 包 含 并 使 用 另 一 个 项 目 也 许 是 第 三 方 库, 或 者 你 独 立 开 发 的, 用 于 多 个 父 项 目 的 库 现 在 问 题 来 了 : 你 想 要 把 它 们 当 做 两 个 独 立 的 项 目, 同 时 又 想 在 一 个 项 目 中 使 用 另 一 个 我 们 举 一 个 例 子 假 设 你 正 在 开 发 一 个 网 站 然 后 创 建 了 Atom 订 阅 你 决 定 使 用 一 个 库, 而 不 是 写 自 己 的 Atom 生 成 代 码 你 可 能 不 得 不 通 过 CPAN 安 装 或 Ruby gem 来 包 含 共 享 库 中 的 代 码, 或 者 将 源 代 码 直 接 拷 贝 到 自 己 的 项 目 中 如 果 将 这 个 库 包 含 进 来, 那 么 无 论 用 何 种 方 式 都 很 难 定 制 它, 部 署 则 更 加 困 难, 因 为 你 必 须 确 保 每 一 个 客 户 端 都 包 含 该 库 如 果 将 代 码 复 制 到 自 己 的 项 目 中, 那 么 你 做 的 任 何 自 定 义 修 改 都 会 使 合 并 上 游 的 改 动 变 得 困 难 Git 通 过 子 模 块 来 解 决 这 个 问 题 子 模 块 允 许 你 将 一 个 Git 仓 库 作 为 另 一 个 Git 仓 库 的 子 目 录 它 能 让 你 将 另 一 个 仓 库 克 隆 到 自 己 的 项 目 中, 同 时 还 保 持 提 交 的 独 立 开 始 使 用 子 模 块 我 们 将 要 演 示 如 何 在 一 个 被 分 成 一 个 主 项 目 与 几 个 子 项 目 的 项 目 上 开 发 我 们 首 先 将 一 个 已 存 在 的 Git 仓 库 添 加 为 正 在 工 作 的 仓 库 的 子 模 块 你 可 以 通 过 在 git submodule add 命 令 后 面 加 上 想 要 跟 踪 的 项 目 URL 来 添 加 新 的 子 模 块 在 本 例 中, 我 们 将 会 添 加 一 个 名 为 DbConnector 的 库 $ git submodule add Cloning into 'DbConnector'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. 默 认 情 况 下, 子 模 块 会 将 子 项 目 放 到 一 个 与 仓 库 同 名 的 目 录 中, 本 例 中 是 DbConnector 如 果 你 想 要 放 到 其 他 地 方, 那 么 可 以 在 命 令 结 尾 添 加 一 个 不 同 的 路 径 如 果 这 时 运 行 git status, 你 会 注 意 到 几 件 事

293 $ git status On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: new file:.gitmodules DbConnector 首 先 应 当 注 意 到 新 的.gitmodules 文 件 该 置 文 件 保 存 了 项 目 URL 与 已 经 拉 取 的 本 地 目 录 之 间 的 映 射 : $ cat.gitmodules [submodule "DbConnector"] path = DbConnector url = 如 果 有 多 个 子 模 块, 该 文 件 中 就 会 有 多 条 记 录 要 重 点 注 意 的 是, 该 文 件 也 像.gitignore 文 件 一 样 受 到 ( 通 过 ) 版 本 控 制 它 会 和 该 项 目 的 其 他 部 分 一 同 被 拉 取 推 送 这 就 是 克 隆 该 项 目 的 人 知 道 去 哪 获 得 子 模 块 的 原 因 NOTE 由 于.gitmodules 文 件 中 的 URL 是 人 们 首 先 尝 试 克 隆 / 拉 取 的 地 方, 因 此 请 尽 可 能 确 保 你 使 用 的 URL 大 家 都 能 访 问 例 如, 若 你 要 使 用 的 推 送 URL 与 他 人 的 拉 取 URL 不 同, 那 么 请 使 用 他 人 能 访 问 到 的 URL 你 也 可 以 根 据 自 己 的 需 要, 通 过 在 本 地 执 行 git config submodule.dbconnector.url < 私 有 URL> 来 覆 盖 这 个 选 项 的 值 如 果 可 行 的 话, 一 个 相 对 路 径 会 很 有 帮 助 在 git status 输 出 中 列 出 的 另 一 个 是 项 目 文 件 夹 记 录 如 果 你 运 行 git diff, 会 看 到 类 似 下 面 的 信 息 : $ git diff --cached DbConnector diff --git a/dbconnector b/dbconnector new file mode index c3f01dc --- /dev/null ,0 +Subproject commit c3f01dc d317dd46284b05b6892c7b29bc 虽 然 DbConnector 是 工 作 目 录 中 的 一 个 子 目 录, 但 Git 还 是 会 将 它 视 作 一 个 子 模 块 当 你 不 在 那 个 目 录 中 时,Git 并 不 会 跟 踪 它 的 内 容, 而 是 将 它 看 作 该 仓 库 中 的 一 个 特 殊 提 交 如 果 你 想 看 到 更 漂 亮 的 差 异 输 出, 可 以 给 git diff 传 递 --submodule 选 项

294 $ git diff --cached --submodule diff --git a/.gitmodules b/.gitmodules new file mode index fc /dev/null +++ b/.gitmodules -0,0 +1,3 +[submodule "DbConnector"] + path = DbConnector + url = Submodule DbConnector c3f01dc (new submodule) 当 你 提 交 时, 会 看 到 类 似 下 面 的 信 息 : $ git commit -am 'added DbConnector module' [master fb9093c] added DbConnector module 2 files changed, 4 insertions(+) create mode gitmodules create mode DbConnector 注 意 DbConnector 记 录 的 模 式 这 是 Git 中 的 一 种 特 殊 模 式, 它 本 质 上 意 味 着 你 是 将 一 次 提 交 记 作 一 项 目 录 记 录 的, 而 非 将 它 记 录 成 一 个 子 目 录 或 者 一 个 文 件 克 隆 含 有 子 模 块 的 项 目 接 下 来 我 们 将 会 克 隆 一 个 含 有 子 模 块 的 项 目 当 你 在 克 隆 这 样 的 项 目 时, 默 认 会 包 含 该 子 模 块 目 录, 但 其 中 还 没 有 任 何 文 件 :

295 $ git clone Cloning into 'MainProject'... remote: Counting objects: 14, done. remote: Compressing objects: 100% (13/13), done. remote: Total 14 (delta 1), reused 13 (delta 0) Unpacking objects: 100% (14/14), done. Checking connectivity... done. $ cd MainProject $ ls -la total 16 drwxr-xr-x 9 schacon staff 306 Sep 17 15:21. drwxr-xr-x 7 schacon staff 238 Sep 17 15:21.. drwxr-xr-x 13 schacon staff 442 Sep 17 15:21.git -rw-r--r-- 1 schacon staff 92 Sep 17 15:21.gitmodules drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector -rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src $ cd DbConnector/ $ ls $ 其 中 有 DbConnector 目 录, 不 过 是 空 的 你 必 须 运 行 两 个 命 令 :git submodule init 用 来 初 始 化 本 地 配 置 文 件, 而 git submodule update 则 从 该 项 目 中 抓 取 所 有 数 据 并 检 出 父 项 目 中 列 出 的 合 适 的 提 交 $ git submodule init Submodule 'DbConnector' ( registered for path 'DbConnector' $ git submodule update Cloning into 'DbConnector'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. Submodule path 'DbConnector': checked out 'c3f01dc d317dd46284b05b6892c7b29bc' 现 在 DbConnector 子 目 录 是 处 在 和 之 前 提 交 时 相 同 的 状 态 了 不 过 还 有 更 简 单 一 点 的 方 式 如 果 给 git clone 命 令 传 递 --recursive 选 项, 它 就 会 自 动 初 始 化 并 更 新 仓 库 中 的 每 一 个 子 模 块

296 $ git clone --recursive Cloning into 'MainProject'... remote: Counting objects: 14, done. remote: Compressing objects: 100% (13/13), done. remote: Total 14 (delta 1), reused 13 (delta 0) Unpacking objects: 100% (14/14), done. Checking connectivity... done. Submodule 'DbConnector' ( registered for path 'DbConnector' Cloning into 'DbConnector'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. Submodule path 'DbConnector': checked out 'c3f01dc d317dd46284b05b6892c7b29bc' 在 包 含 子 模 块 的 项 目 上 工 作 现 在 我 们 有 一 份 包 含 子 模 块 的 项 目 副 本, 我 们 将 会 同 时 在 主 项 目 和 子 模 块 项 目 上 与 队 员 协 作 拉 取 上 游 修 改 在 项 目 中 使 用 子 模 块 的 最 简 模 型, 就 是 只 使 用 子 项 目 并 不 时 地 获 取 更 新, 而 并 不 在 你 的 检 出 中 进 行 任 何 更 改 我 们 来 看 一 个 简 单 的 例 子 如 果 想 要 在 子 模 块 中 查 看 新 工 作, 可 以 进 入 到 目 录 中 运 行 git fetch 与 git merge, 合 并 上 游 分 支 来 更 新 本 地 代 码 $ git fetch From c3f01dc..d0354fc master -> origin/master $ git merge origin/master Updating c3f01dc..d0354fc Fast-forward scripts/connect.sh 1 + src/db.c files changed, 2 insertions(+) 如 果 你 现 在 返 回 到 主 项 目 并 运 行 git diff --submodule, 就 会 看 到 子 模 块 被 更 新 的 同 时 获 得 了 一 个 包 含 新 添 加 提 交 的 列 表 如 果 你 不 想 每 次 运 行 git diff 时 都 输 入 --submodle, 那 么 可 以 将 diff.submodule 设 置 为 log 来 将 其 作 为 默 认 行 为

297 $ git config --global diff.submodule log $ git diff Submodule DbConnector c3f01dc..d0354fc: > more efficient db routine > better connection routine 如 果 在 此 时 提 交, 那 么 你 会 将 子 模 块 锁 定 为 其 他 人 更 新 时 的 新 代 码 如 果 你 不 想 在 子 目 录 中 手 动 抓 取 与 合 并, 那 么 还 有 种 更 容 易 的 方 式 运 行 git submodule update --remote,git 将 会 进 入 子 模 块 然 后 抓 取 并 更 新 $ git submodule update --remote DbConnector remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 2), reused 4 (delta 2) Unpacking objects: 100% (4/4), done. From 3f d0354fc master -> origin/master Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644' 此 命 令 默 认 会 假 定 你 想 要 更 新 并 检 出 子 模 块 仓 库 的 master 分 支 不 过 你 也 可 以 设 置 为 想 要 的 其 他 分 支 例 如, 你 想 要 DbConnector 子 模 块 跟 踪 仓 库 的 stable 分 支, 那 么 既 可 以 在.gitmodules 文 件 中 设 置 ( 这 样 其 他 人 也 可 以 跟 踪 它 ), 也 可 以 只 在 本 地 的.git/config 文 件 中 设 置 让 我 们 在.gitmodules 文 件 中 设 置 它 : $ git config -f.gitmodules submodule.dbconnector.branch stable $ git submodule update --remote remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 2), reused 4 (delta 2) Unpacking objects: 100% (4/4), done. From 27cf5d3..c87d55d stable -> origin/stable Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687' 如 果 不 用 -f.gitmodules 选 项, 那 么 它 只 会 为 你 做 修 改 但 是 在 仓 库 中 保 留 跟 踪 信 息 更 有 意 义 一 些, 因 为 其 他 人 也 可 以 得 到 同 样 的 效 果 这 时 我 们 运 行 git status,git 会 显 示 子 模 块 中 有 新 提 交

298 $ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: modified:.gitmodules DbConnector (new commits) no changes added to commit (use "git add" and/or "git commit -a") 如 果 你 设 置 了 配 置 选 项 status.submodulesummary,git 也 会 显 示 你 的 子 模 块 的 更 改 摘 要 : $ git config status.submodulesummary 1 $ git status On branch master Your branch is up-to-date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: modified:.gitmodules DbConnector (new commits) Submodules changed but not updated: * DbConnector c3f01dc...c87d55d (4): > catch non-null terminated lines 这 时 如 果 运 行 git diff, 可 以 看 到 我 们 修 改 了.gitmodules 文 件, 同 时 还 有 几 个 已 拉 取 的 提 交 需 要 提 交 到 我 们 自 己 的 子 模 块 项 目 中

299 $ git diff diff --git a/.gitmodules b/.gitmodules index 6fc0b3d..fd1cc a/.gitmodules +++ b/.gitmodules -1,3 +1,4 [submodule "DbConnector"] path = DbConnector url = + branch = stable Submodule DbConnector c3f01dc..c87d55d: > catch non-null terminated lines > more robust error handling > more efficient db routine > better connection routine 这 非 常 有 趣, 因 为 我 们 可 以 直 接 看 到 将 要 提 交 到 子 模 块 中 的 提 交 日 志 提 交 之 后, 你 也 可 以 运 行 git log -p 查 看 这 个 信 息 $ git log -p --submodule commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae Author: Scott Chacon <schacon@gmail.com> Date: Wed Sep 17 16:37: updating DbConnector for bug fixes diff --git a/.gitmodules b/.gitmodules index 6fc0b3d..fd1cc a/.gitmodules ,3 [submodule "DbConnector"] path = DbConnector url = + branch = stable Submodule DbConnector c3f01dc..c87d55d: > catch non-null terminated lines > more robust error handling > more efficient db routine > better connection routine 当 运 行 git submodule update --remote 时,Git 默 认 会 尝 试 更 新 所 有 子 模 块, 所 以 如 果 有 很 多 子 模 块 的 话, 你 可 以 传 递 想 要 更 新 的 子 模 块 的 名 字

300 在 子 模 块 上 工 作 你 很 有 可 能 正 在 使 用 子 模 块, 因 为 你 确 实 想 在 子 模 块 中 编 写 代 码 的 同 时, 还 想 在 主 项 目 上 编 写 代 码 ( 或 者 跨 子 模 块 工 作 ) 否 则 你 大 概 只 能 用 简 单 的 依 赖 管 理 系 统 ( 如 Maven 或 Rubygems) 来 替 代 了 现 在 我 们 将 通 过 一 个 例 子 来 演 示 如 何 在 子 模 块 与 主 项 目 中 同 时 做 修 改, 以 及 如 何 同 时 提 交 与 发 布 那 些 修 改 到 目 前 为 止, 当 我 们 运 行 git submodule update 从 子 模 块 仓 库 中 抓 取 修 改 时,Git 将 会 获 得 这 些 改 动 并 更 新 子 目 录 中 的 文 件, 但 是 会 将 子 仓 库 留 在 一 个 称 作 游 离 的 HEAD 的 状 态 这 意 味 着 没 有 本 地 工 作 分 支 ( 例 如 master ) 跟 踪 改 动 所 以 你 做 的 任 何 改 动 都 不 会 被 跟 踪 为 了 将 子 模 块 设 置 得 更 容 易 进 入 并 修 改, 你 需 要 做 两 件 事 首 先, 进 入 每 个 子 模 块 并 检 出 其 相 应 的 工 作 分 支 接 着, 若 你 做 了 更 改 就 需 要 告 诉 Git 它 该 做 什 么, 然 后 运 行 git submodule update --remote 来 从 上 游 拉 取 新 工 作 你 可 以 选 择 将 它 们 合 并 到 你 的 本 地 工 作 中, 也 可 以 尝 试 将 你 的 工 作 变 基 到 新 的 更 改 上 首 先, 让 我 们 进 入 子 模 块 目 录 然 后 检 出 一 个 分 支 $ git checkout stable Switched to branch 'stable' 然 后 尝 试 用 merge 选 项 为 了 手 动 指 定 它, 我 们 只 需 给 update 添 加 --merge 选 项 即 可 这 时 我 们 将 会 看 到 服 务 器 上 的 这 个 子 模 块 有 一 个 改 动 并 且 它 被 合 并 了 进 来 $ git submodule update --remote --merge remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 2), reused 4 (delta 2) Unpacking objects: 100% (4/4), done. From c87d55d..92c7337 stable -> origin/stable Updating c87d55d..92c7337 Fast-forward src/main.c file changed, 1 insertion(+) Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea' 如 果 我 们 进 入 DbConnector 目 录, 可 以 发 现 新 的 改 动 已 经 合 并 入 本 地 stable 分 支 现 在 让 我 们 看 看 当 我 们 对 库 做 一 些 本 地 的 改 动 而 同 时 其 他 人 推 送 另 外 一 个 修 改 到 上 游 时 会 发 生 什 么

301 $ cd DbConnector/ $ vim src/db.c $ git commit -am 'unicode support' [stable f906e16] unicode support 1 file changed, 1 insertion(+) 如 果 我 们 现 在 更 新 子 模 块, 就 会 看 到 当 我 们 在 本 地 做 了 更 改 时 上 游 也 有 一 个 改 动, 我 们 需 要 将 它 并 入 本 地 $ git submodule update --remote --rebase First, rewinding head to replay your work on top of it... Applying: unicode support Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94' 如 果 你 忘 记 --rebase 或 --merge,git 会 将 子 模 块 更 新 为 服 务 器 上 的 状 态 并 且 会 将 项 目 重 置 为 一 个 游 离 的 HEAD 状 态 $ git submodule update --remote Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94' 即 便 这 真 的 发 生 了 也 不 要 紧, 你 只 需 回 到 目 录 中 再 次 检 出 你 的 分 支 ( 即 还 包 含 着 你 的 工 作 的 分 支 ) 然 后 手 动 地 合 并 或 变 基 origin/stable( 或 任 何 一 个 你 想 要 的 远 程 分 支 ) 就 行 了 如 果 你 没 有 提 交 子 模 块 的 改 动, 那 么 运 行 一 个 子 模 块 更 新 也 不 会 出 现 问 题, 此 时 Git 会 只 抓 取 更 改 而 并 不 会 覆 盖 子 模 块 目 录 中 未 保 存 的 工 作 $ git submodule update --remote remote: Counting objects: 4, done. remote: Compressing objects: 100% (3/3), done. remote: Total 4 (delta 0), reused 4 (delta 0) Unpacking objects: 100% (4/4), done. From 5d60ef9..c75e92a stable -> origin/stable error: Your local changes to the following files would be overwritten by checkout: scripts/setup.sh Please, commit your changes or stash them before you can switch branches. Aborting Unable to checkout 'c75e92a2b3855c9e5b66f d9db204aca' in submodule path 'DbConnector'

302 如 果 你 做 了 一 些 与 上 游 改 动 冲 突 的 改 动, 当 运 行 更 新 时 Git 会 让 你 知 道 $ git submodule update --remote --merge Auto-merging scripts/setup.sh CONFLICT (content): Merge conflict in scripts/setup.sh Recorded preimage for 'scripts/setup.sh' Automatic merge failed; fix conflicts and then commit the result. Unable to merge 'c75e92a2b3855c9e5b66f d9db204aca' in submodule path 'DbConnector' 你 可 以 进 入 子 模 块 目 录 中 然 后 就 像 平 时 那 样 修 复 冲 突 发 布 子 模 块 改 动 现 在 我 们 的 子 模 块 目 录 中 有 一 些 改 动 其 中 有 一 些 是 我 们 通 过 更 新 从 上 游 引 入 的, 而 另 一 些 是 本 地 生 成 的, 由 于 我 们 还 没 有 推 送 它 们, 所 以 对 任 何 其 他 人 都 不 可 用 $ git diff Submodule DbConnector c87d55d..82d2ad3: > Merge from origin/stable > updated setup script > unicode support > remove unnecessary method > add new option for conn pooling 如 果 我 们 在 主 项 目 中 提 交 并 推 送 但 并 不 推 送 子 模 块 上 的 改 动, 其 他 尝 试 检 出 我 们 修 改 的 人 会 遇 到 麻 烦, 因 为 他 们 无 法 得 到 依 赖 的 子 模 块 改 动 那 些 改 动 只 存 在 于 我 们 本 地 的 拷 贝 中 为 了 确 保 这 不 会 发 生, 你 可 以 让 Git 在 推 送 到 主 项 目 前 检 查 所 有 子 模 块 是 否 已 推 送 git push 命 令 接 受 可 以 设 置 为 check 或 on-demand 的 --recurse-submodules 参 数 如 果 任 何 提 交 的 子 模 块 改 动 没 有 推 送 那 么 check 选 项 会 直 接 使 push 操 作 失 败

303 $ git push --recurse-submodules=check The following submodule paths contain changes that can not be found on any remote: DbConnector Please try git push --recurse-submodules=on-demand or cd to the path and use git push to push them to a remote. 如 你 所 见, 它 也 给 我 们 了 一 些 有 用 的 建 议, 指 导 接 下 来 该 如 何 做 最 简 单 的 选 项 是 进 入 每 一 个 子 模 块 中 然 后 手 动 推 送 到 远 程 仓 库, 确 保 它 们 能 被 外 部 访 问 到, 之 后 再 次 尝 试 这 次 推 送 另 一 个 选 项 是 使 用 on-demand 值, 它 会 尝 试 为 你 这 样 做 $ git push --recurse-submodules=on-demand Pushing submodule 'DbConnector' Counting objects: 9, done. Delta compression using up to 8 threads. Compressing objects: 100% (8/8), done. Writing objects: 100% (9/9), 917 bytes 0 bytes/s, done. Total 9 (delta 3), reused 0 (delta 0) To c75e92a..82d2ad3 stable -> stable Counting objects: 2, done. Delta compression using up to 8 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (2/2), 266 bytes 0 bytes/s, done. Total 2 (delta 1), reused 0 (delta 0) To 3d6d338..9a377d1 master -> master 如 你 所 见,Git 进 入 到 DbConnector 模 块 中 然 后 在 推 送 主 项 目 前 推 送 了 它 如 果 那 个 子 模 块 因 为 某 些 原 因 推 送 失 败, 主 项 目 也 会 推 送 失 败 合 并 子 模 块 改 动 如 果 你 其 他 人 同 时 改 动 了 一 个 子 模 块 引 用, 那 么 可 能 会 遇 到 一 些 问 题 也 就 是 说, 如 果 子 模 块 的 历 史 已 经 分 叉 并 且 在 父 项 目 中 分 别 提 交 到 了 分 叉 的 分 支 上, 那 么 你 需 要 做 一 些 工 作 来 修 复 它

304 如 果 一 个 提 交 是 另 一 个 的 直 接 祖 先 ( 一 个 快 进 式 合 并 ), 那 么 Git 会 简 单 地 选 择 之 后 的 提 交 来 合 并, 这 样 没 什 么 问 题 不 过,Git 甚 至 不 会 尝 试 去 进 行 一 次 简 单 的 合 并 如 果 子 模 块 提 交 已 经 分 叉 且 需 要 合 并, 那 你 会 得 到 类 似 下 面 的 信 息 : $ git pull remote: Counting objects: 2, done. remote: Compressing objects: 100% (1/1), done. remote: Total 2 (delta 1), reused 2 (delta 1) Unpacking objects: 100% (2/2), done. From 9a377d1..eb974f8 master -> origin/master Fetching submodule DbConnector warning: Failed to merge submodule DbConnector (merge following commits not found) Auto-merging DbConnector CONFLICT (submodule): Merge conflict in DbConnector Automatic merge failed; fix conflicts and then commit the result. 所 以 本 质 上 Git 在 这 里 指 出 了 子 模 块 历 史 中 的 两 个 分 支 记 录 点 已 经 分 叉 并 且 需 要 合 并 它 将 其 解 释 为 merge following commits not found ( 未 找 到 接 下 来 需 要 合 并 的 提 交 ), 虽 然 这 有 点 令 人 困 惑, 不 过 之 后 我 们 会 解 释 为 什 么 是 这 样 为 了 解 决 这 个 问 题, 你 需 要 弄 清 楚 子 模 块 应 该 处 于 哪 种 状 态 奇 怪 的 是,Git 并 不 会 给 你 多 少 能 帮 你 摆 脱 困 境 的 信 息, 甚 至 连 两 边 提 交 历 史 中 的 SHA-1 值 都 没 有 幸 运 的 是, 这 很 容 易 解 决 如 果 你 运 行 git diff, 就 会 得 到 试 图 合 并 的 两 个 分 支 中 记 录 的 提 交 的 SHA-1 值 $ git diff diff --cc DbConnector index eb41d76,c a/dbconnector +++ b/dbconnector 所 以, 在 本 例 中,eb41d76 是 我 们 的 子 模 块 中 大 家 共 有 的 提 交, 而 c 是 上 游 拥 有 的 提 交 如 果 我 们 进 入 子 模 块 目 录 中, 它 应 该 已 经 在 eb41d76 上 了, 因 为 合 并 没 有 动 过 它 如 果 不 是 的 话, 无 论 什 么 原 因, 你 都 可 以 简 单 地 创 建 并 检 出 一 个 指 向 它 的 分 支 来 自 另 一 边 的 提 交 的 SHA-1 值 比 较 重 要 它 是 需 要 你 来 合 并 解 决 的 你 可 以 尝 试 直 接 通 过 SHA-1 合 并, 也 可 以 为 它 创 建 一 个 分 支 然 后 尝 试 合 并 我 们 建 议 后 者, 哪 怕 只 是 为 了 一 个 更 漂 亮 的 合 并 提 交 信 息 所 以, 我 们 将 会 进 入 子 模 块 目 录, 基 于 git diff 的 第 二 个 SHA 创 建 一 个 分 支 然 后 手 动 合 并

305 $ cd DbConnector $ git rev-parse HEAD eb41d764bccf88be77aced643c13a7fa $ git branch try-merge c (DbConnector) $ git merge try-merge Auto-merging src/main.c CONFLICT (content): Merge conflict in src/main.c Recorded preimage for 'src/main.c' Automatic merge failed; fix conflicts and then commit the result. 我 们 在 这 儿 得 到 了 一 个 真 正 的 合 并 冲 突, 所 以 如 果 想 要 解 决 并 提 交 它, 那 么 只 需 简 单 地 通 过 结 果 来 更 新 主 项 目 $ vim src/main.c 1 $ git add src/main.c $ git commit -am 'merged our changes' Recorded resolution for 'src/main.c'. [master 9fd905e] merged our changes $ cd.. 2 $ git diff 3 diff --cc DbConnector index eb41d76,c a/dbconnector ,1-1,1 - Subproject commit eb41d764bccf88be77aced643c13a7fa Subproject commit c afbbe1f58b ead08f4b7e6d1d ++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a $ git add DbConnector 4 $ git commit -m "Merge Tom's Changes" 5 [master 10d2c60] Merge Tom's Changes 1 首 先 解 决 冲 突 2 然 后 返 回 到 主 项 目 目 录 中 3 再 次 检 查 SHA-1 值 4 解 决 冲 突 的 子 模 块 记 录 5 提 交 我 们 的 合 并 这 可 能 会 让 你 有 点 儿 困 惑, 但 它 确 实 不 难 有 趣 的 是,Git 还 能 处 理 另 一 种 情 况 如 果 子 模 块 目 录 中 存 在 着 这 样 一 个 合 并 提 交, 它 的 历 史 中 包 含 了 的 两 边 的

306 提 交, 那 么 Git 会 建 议 你 将 它 作 为 一 个 可 行 的 解 决 方 案 它 看 到 有 人 在 子 模 块 项 目 的 某 一 点 上 合 并 了 包 含 这 两 次 提 交 的 分 支, 所 以 你 可 能 想 要 那 个 这 就 是 为 什 么 前 面 的 错 误 信 息 是 merge following commits not found, 因 为 它 不 能 这 样 做 它 让 人 困 惑 是 因 为 谁 能 想 到 它 会 尝 试 这 样 做? 如 果 它 找 到 了 一 个 可 以 接 受 的 合 并 提 交, 你 会 看 到 类 似 下 面 的 信 息 : $ git merge origin/master warning: Failed to merge submodule DbConnector (not fast-forward) Found a possible merge resolution for the submodule: 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes If this is correct simply add it to the index for example by using: git update-index --cacheinfo fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector" which will accept this suggestion. Auto-merging DbConnector CONFLICT (submodule): Merge conflict in DbConnector Automatic merge failed; fix conflicts and then commit the result. 它 会 建 议 你 更 新 索 引, 就 像 你 运 行 了 git add 那 样, 这 样 会 清 除 冲 突 然 后 提 交 不 过 你 可 能 不 应 该 这 样 做 你 可 以 轻 松 地 进 入 子 模 块 目 录, 查 看 差 异 是 什 么, 快 进 到 这 次 提 交, 恰 当 地 测 试, 然 后 提 交 它 $ cd DbConnector/ $ git merge 9fd905e Updating eb41d76..9fd905e Fast-forward $ cd.. $ git add DbConnector $ git commit -am 'Fast forwarded to a common submodule child' 这 些 命 令 完 成 了 同 一 件 事, 但 是 通 过 这 种 方 式 你 至 少 可 以 验 证 工 作 是 否 有 效, 以 及 当 你 在 完 成 时 可 以 确 保 子 模 块 目 录 中 有 你 的 代 码 子 模 块 技 巧 你 可 以 做 几 件 事 情 来 让 用 子 模 块 工 作 轻 松 一 点 儿

307 子 模 块 遍 历 有 一 个 foreach 子 模 块 命 令, 它 能 在 每 一 个 子 模 块 中 运 行 任 意 命 令 如 果 项 目 中 包 含 了 大 量 子 模 块, 这 会 非 常 有 用 例 如, 假 设 我 们 想 要 开 始 开 发 一 项 新 功 能 或 者 修 复 一 些 错 误, 并 且 需 要 在 几 个 子 模 块 内 工 作 我 们 可 以 轻 松 地 保 存 所 有 子 模 块 的 工 作 进 度 $ git submodule foreach 'git stash' Entering 'CryptoLibrary' No local changes to save Entering 'DbConnector' Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable HEAD is now at 82d2ad3 Merge from origin/stable 然 后 我 们 可 以 创 建 一 个 新 分 支, 并 将 所 有 子 模 块 都 切 换 过 去 $ git submodule foreach 'git checkout -b featurea' Entering 'CryptoLibrary' Switched to a new branch 'featurea' Entering 'DbConnector' Switched to a new branch 'featurea' 你 应 该 明 白 能 够 生 成 一 个 主 项 目 与 所 有 子 项 目 的 改 动 的 统 一 差 异 是 非 常 有 用 的

308 $ git diff; git submodule foreach 'git diff' Submodule DbConnector contains modified content diff --git a/src/main.c b/src/main.c index 210f1ae..1f0acdc a/src/main.c +++ b/src/main.c -245,6 +245,8 static int handle_alias(int *argcp, const char ***argv) commit_pager_choice(); + url = url_decode(url_orig); + /* build alias_argv */ alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1)); alias_argv[0] = alias_string + 1; Entering 'DbConnector' diff --git a/src/db.c b/src/db.c index 1aaefb a/src/db.c ,6 char *url_decode_mem(const char *url, int len) return url_decode_internal(&url, len, NULL, &out, 0); } +char *url_decode(const char *url) +{ + return url_decode_mem(url, strlen(url)); +} + char *url_decode_parameter_name(const char **query) { struct strbuf out = STRBUF_INIT; 在 这 里, 我 们 看 到 子 模 块 中 定 义 了 一 个 函 数 并 在 主 项 目 中 调 用 了 它 这 明 显 是 个 简 化 了 的 例 子, 但 是 希 望 它 能 让 你 明 白 这 种 方 法 的 用 处 有 用 的 别 名 你 可 能 想 为 其 中 一 些 命 令 设 置 别 名, 因 为 它 们 可 能 会 非 常 长 而 你 又 不 能 设 置 选 项 作 为 它 们 的 默 认 选 项 我 们 在 Git 别 名 介 绍 了 设 置 Git 别 名, 但 是 如 果 你 计 划 在 Git 中 大 量 使 用 子 模 块 的 话, 这 里 有 一 些 例 子 $ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'" $ git config alias.spush 'push --recurse-submodules=on-demand' $ git config alias.supdate 'submodule update --remote --merge'

309 这 样 当 你 想 要 更 新 子 模 块 时 可 以 简 单 地 运 行 git supdate, 或 git spush 检 查 子 模 块 依 赖 后 推 送 子 模 块 的 问 题 然 而 使 用 子 模 块 还 是 有 一 些 小 问 题 例 如 在 有 子 模 块 的 项 目 中 切 换 分 支 可 能 会 造 成 麻 烦 如 果 你 创 建 一 个 新 分 支, 在 其 中 添 加 一 个 子 模 块, 之 后 切 换 到 没 有 该 子 模 块 的 分 支 上 时, 你 仍 然 会 有 一 个 还 未 跟 踪 的 子 模 块 目 录 $ git checkout -b add-crypto Switched to a new branch 'add-crypto' $ git submodule add Cloning into 'CryptoLibrary' $ git commit -am 'adding crypto library' [add-crypto ] adding crypto library 2 files changed, 4 insertions(+) create mode CryptoLibrary $ git checkout master warning: unable to rmdir CryptoLibrary: Directory not empty Switched to branch 'master' Your branch is up-to-date with 'origin/master'. $ git status On branch master Your branch is up-to-date with 'origin/master'. Untracked files: (use "git add <file>..." to include in what will be committed) CryptoLibrary/ nothing added to commit but untracked files present (use "git add" to track) 移 除 那 个 目 录 并 不 困 难, 但 是 有 一 个 目 录 在 那 儿 会 让 人 有 一 点 困 惑 如 果 你 移 除 它 然 后 切 换 回 有 那 个 子 模 块 的 分 支, 需 要 运 行 submodule update --init 来 重 新 建 立 和 填 充

310 $ git clean -fdx Removing CryptoLibrary/ $ git checkout add-crypto Switched to branch 'add-crypto' $ ls CryptoLibrary/ $ git submodule update --init Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e af' $ ls CryptoLibrary/ Makefile includes scripts src 再 说 一 遍, 这 真 的 不 难, 只 是 会 让 人 有 点 儿 困 惑 另 一 个 主 要 的 告 诫 是 许 多 人 遇 到 了 将 子 目 录 转 换 为 子 模 块 的 问 题 如 果 你 在 项 目 中 已 经 跟 踪 了 一 些 文 件, 然 后 想 要 将 它 们 移 动 到 一 个 子 模 块 中, 那 么 请 务 必 小 心, 否 则 Git 会 对 你 发 脾 气 假 设 项 目 内 有 一 些 文 件 在 子 目 录 中, 你 想 要 将 其 转 换 为 一 个 子 模 块 如 果 删 除 子 目 录 然 后 运 行 submodule add,git 会 朝 你 大 喊 : $ rm -Rf CryptoLibrary/ $ git submodule add 'CryptoLibrary' already exists in the index 你 必 须 要 先 取 消 暂 存 CryptoLibrary 目 录 然 后 才 可 以 添 加 子 模 块 : $ git rm -r CryptoLibrary $ git submodule add Cloning into 'CryptoLibrary'... remote: Counting objects: 11, done. remote: Compressing objects: 100% (10/10), done. remote: Total 11 (delta 0), reused 11 (delta 0) Unpacking objects: 100% (11/11), done. Checking connectivity... done. 现 在 假 设 你 在 一 个 分 支 下 做 了 这 样 的 工 作 如 果 尝 试 切 换 回 的 分 支 中 那 些 文 件 还 在 子 目 录 而 非 子 模 块 中 时 - 你 会 得 到 这 个 错 误 :

311 $ git checkout master error: The following untracked working tree files would be overwritten by checkout: CryptoLibrary/Makefile CryptoLibrary/includes/crypto.h... Please move or remove them before you can switch branches. Aborting 你 可 以 通 过 check -f 来 强 制 切 换, 但 是 要 小 心, 如 果 其 中 还 有 未 保 存 的 修 改, 这 个 命 令 会 把 它 们 覆 盖 掉 $ git checkout -f master warning: unable to rmdir CryptoLibrary: Directory not empty Switched to branch 'master' 当 你 切 换 回 来 之 后, 因 为 某 些 原 因 你 得 到 了 一 个 空 的 CryptoLibrary 目 录, 并 且 git submodule update 也 无 法 修 复 它 你 需 要 进 入 到 子 模 块 目 录 中 运 行 git checkout. 来 找 回 所 有 的 文 件 你 也 可 以 通 过 submodule foreach 脚 本 来 为 多 个 子 模 块 运 行 它 要 特 别 注 意 的 是, 近 来 子 模 块 会 将 它 们 的 所 有 Git 数 据 保 存 在 顶 级 项 目 的.git 目 录 中, 所 以 不 像 旧 版 本 的 Git, 摧 毁 一 个 子 模 块 目 录 并 不 会 丢 失 任 何 提 交 或 分 支 拥 有 了 这 些 工 具, 使 用 子 模 块 会 成 为 可 以 在 几 个 相 关 但 却 分 离 的 项 目 上 同 时 开 发 的 相 当 简 单 有 效 的 方 法 打 包 虽 然 我 们 已 经 了 解 了 网 络 传 输 Git 数 据 的 常 用 方 法 ( 如 HTTP,SSH 等 ), 但 还 有 另 外 一 种 不 太 常 见 却 又 十 分 有 用 的 方 式 Git 可 以 将 它 的 数 据 打 包 到 一 个 文 件 中 这 在 许 多 场 景 中 都 很 有 用 有 可 能 你 的 网 络 中 断 了, 但 你 又 希 望 将 你 的 提 交 传 给 你 的 合 作 者 们 可 能 你 不 在 办 公 网 中 并 且 出 于 安 全 考 虑 没 有 给 你 接 入 内 网 的 权 限 可 能 你 的 无 线 有 线 网 卡 坏 掉 了 可 能 你 现 在 没 有 共 享 服 务 器 的 权 限, 你 又 希 望 通 过 邮 件 将 更 新 发 送 给 别 人, 却 不 希 望 通 过 format-patch 的 方 式 传 输 40 个 提 交 这 些 情 况 下 git bundle 就 会 很 有 用 bundle 命 令 会 将 git push 命 令 所 传 输 的 所 有 内 容 打 包 成 一 个 二 进 制 文 件, 你 可 以 将 这 个 文 件 通 过 邮 件 或 者 闪 存 传 给 其 他 人, 然 后 解 包 到 其 他 的 仓 库 中 来 看 看 一 个 简 单 的 例 子 假 设 你 有 一 个 包 含 两 个 提 交 的 仓 库 :

312 $ git log commit 9a466c572fe88b195efd356c3f2bbeccdb Author: Scott Chacon Date: Wed Mar 10 07:34: second commit commit b1ec3248f39900d2a406049d762aa68e9641be25 Author: Scott Chacon Date: Wed Mar 10 07:34: first commit 如 果 你 想 把 这 个 仓 库 发 送 给 其 他 人 但 你 没 有 其 他 仓 库 的 权 限, 或 者 就 是 懒 得 新 建 一 个 仓 库, 你 就 可 以 用 git bundle create 命 令 来 打 包 $ git bundle create repo.bundle HEAD master Counting objects: 6, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (6/6), 441 bytes, done. Total 6 (delta 0), reused 0 (delta 0) 然 后 你 就 会 有 一 个 名 为 repo.bundle 的 文 件, 该 文 件 包 含 了 所 有 重 建 该 仓 库 master 分 支 所 需 的 数 据 在 使 用 bundle 命 令 时, 你 需 要 列 出 所 有 你 希 望 打 包 的 引 用 或 者 提 交 的 区 间 如 果 你 希 望 这 个 仓 库 可 以 在 别 处 被 克 隆, 你 应 该 像 例 子 中 那 样 增 加 一 个 HEAD 引 用 你 可 以 将 这 个 repo.bundle 文 件 通 过 邮 件 或 者 U 盘 传 给 别 人 另 一 方 面, 假 设 别 人 传 给 你 一 个 repo.bundle 文 件 并 希 望 你 在 这 个 项 目 上 工 作 你 可 以 从 这 个 二 进 制 文 件 中 克 隆 出 一 个 目 录, 就 像 从 一 个 URL 克 隆 一 样 $ git clone repo.bundle repo Initialized empty Git repository in /private/tmp/bundle/repo/.git/ $ cd repo $ git log --oneline 9a466c5 second commit b1ec324 first commit 如 果 你 在 打 包 时 没 有 包 含 HEAD 引 用, 你 还 需 要 在 命 令 后 指 定 一 个 -b master 或 者 其 他 被 引 入 的 分 支, 否 则 Git 不 知 道 应 该 检 出 哪 一 个 分 支 现 在 假 设 你 提 交 了 3 个 修 订, 并 且 要 用 邮 件 或 者 U 盘 将 新 的 提 交 放 在 一 个 包 里 传 回 去

313 $ git log --oneline 71b84da last commit - second repo c99cf5b fourth commit - second repo 7011d3d third commit - second repo 9a466c5 second commit b1ec324 first commit 首 先 我 们 需 要 确 认 我 们 希 望 被 打 包 的 提 交 区 间 和 网 络 协 议 不 太 一 样, 网 络 协 议 会 自 动 计 算 出 所 需 传 输 的 最 小 数 据 集, 而 我 们 需 要 手 动 计 算 当 然 你 可 以 像 上 面 那 样 将 整 个 仓 库 打 包, 但 最 好 仅 仅 打 包 变 更 的 部 分 就 是 我 们 刚 刚 在 本 地 做 的 3 个 提 交 为 了 实 现 这 个 目 标, 你 需 要 计 算 出 差 别 就 像 我 们 在 提 交 区 间 介 绍 的, 你 有 很 多 种 方 式 去 指 明 一 个 提 交 区 间 我 们 可 以 使 用 origin/master..master 或 者 master ^origin/master 之 类 的 方 法 来 获 取 那 3 个 在 我 们 的 master 分 支 而 不 在 原 始 仓 库 中 的 提 交 你 可 以 用 log 命 令 来 测 试 $ git log --oneline master ^origin/master 71b84da last commit - second repo c99cf5b fourth commit - second repo 7011d3d third commit - second repo 这 样 就 获 取 到 我 们 希 望 被 打 包 的 提 交 列 表, 让 我 们 将 这 些 提 交 打 包 我 们 可 以 用 git bundle create 命 令, 加 上 我 们 想 用 的 文 件 名, 以 及 要 打 包 的 提 交 区 间 $ git bundle create commits.bundle master ^9a466c5 Counting objects: 11, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (9/9), 775 bytes, done. Total 9 (delta 0), reused 0 (delta 0) 现 在 在 我 们 的 目 录 下 会 有 一 个 commits.bundle 文 件 如 果 我 们 把 这 个 文 件 发 送 给 我 们 的 合 作 者, 她 可 以 将 这 个 文 件 导 入 到 原 始 的 仓 库 中, 即 使 在 这 期 间 已 经 有 其 他 的 工 作 提 交 到 这 个 仓 库 中 当 她 拿 到 这 个 包 时, 她 可 以 在 导 入 到 仓 库 之 前 查 看 这 个 包 里 包 含 了 什 么 内 容 bundle verify 命 令 可 以 检 查 这 个 文 件 是 否 是 一 个 合 法 的 Git 包, 是 否 拥 有 共 同 的 祖 先 来 导 入

314 $ git bundle verify../commits.bundle The bundle contains 1 ref 71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master The bundle requires these 1 ref 9a466c572fe88b195efd356c3f2bbeccdb second commit../commits.bundle is okay 如 果 打 包 工 具 仅 仅 把 最 后 两 个 提 交 打 包, 而 不 是 三 个, 原 始 的 仓 库 是 无 法 导 入 这 个 包 的, 因 为 这 个 包 缺 失 了 必 要 的 提 交 记 录 这 时 候 verify 的 输 出 类 似 : $ git bundle verify../commits-bad.bundle error: Repository lacks these prerequisite commits: error: 7011d3d8fc200abe0ad561c011c3852a4b7bbe95 third commit - second repo 而 我 们 的 第 一 个 包 是 合 法 的, 所 以 我 们 可 以 从 这 个 包 里 提 取 出 提 交 如 果 你 想 查 看 这 边 包 里 可 以 导 入 哪 些 分 支, 同 样 有 一 个 命 令 可 以 列 出 这 些 顶 端 : $ git bundle list-heads../commits.bundle 71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master verify 子 命 令 同 样 可 以 告 诉 你 有 哪 些 顶 端 该 功 能 的 目 的 是 查 看 哪 些 是 可 以 被 拉 入 的, 所 以 你 可 以 使 用 fetch 或 者 pull 命 令 从 包 中 导 入 提 交 这 里 我 们 要 从 包 中 取 出 master 分 支 到 我 们 仓 库 中 的 other-master 分 支 : $ git fetch../commits.bundle master:other-master From../commits.bundle * [new branch] master -> other-master 可 以 看 到 我 们 已 经 将 提 交 导 入 到 other-master 分 支, 以 及 在 这 期 间 我 们 自 己 在 master 分 支 上 的 提 交 $ git log --oneline --decorate --graph --all * 8255d41 (HEAD, master) third commit - first repo * 71b84da (other-master) last commit - second repo * c99cf5b fourth commit - second repo * 7011d3d third commit - second repo / * 9a466c5 second commit * b1ec324 first commit 因 此, 当 你 在 没 有 合 适 的 网 络 或 者 可 共 享 仓 库 的 情 况 下,git bundle 很 适 合 用 于 共 享 或 者 网 络 类 型 的 操 作

315 替 换 Git 对 象 是 不 可 改 变 的, 但 它 提 供 一 种 有 趣 的 方 式 来 用 其 他 对 象 假 装 替 换 数 据 库 中 的 Git 对 象 replace 命 令 可 以 让 你 在 Git 中 指 定 一 个 对 象 并 可 以 声 称 每 次 你 遇 到 这 个 Git 对 象 时, 假 装 它 是 其 他 的 东 西 在 你 用 一 个 不 同 的 提 交 替 换 历 史 中 的 一 个 提 交 时, 这 会 非 常 有 用 例 如, 你 有 一 个 大 型 的 代 码 历 史 并 想 把 自 己 的 仓 库 分 成 一 个 短 的 历 史 和 一 个 更 大 更 长 久 的 历 史, 短 历 史 供 新 的 开 发 者 使 用, 后 者 给 喜 欢 数 据 挖 掘 的 人 使 用 你 可 以 通 过 用 新 仓 库 中 最 早 的 提 交 replace 老 仓 库 中 最 新 的 提 交 来 连 接 历 史, 这 种 方 式 可 以 把 一 条 历 史 移 植 到 其 他 历 史 上 这 意 味 着 你 不 用 在 新 历 史 中 真 正 替 换 每 一 个 提 交 ( 因 为 历 史 来 源 会 影 响 SHA 的 值 ), 你 可 以 加 入 他 们 让 我 们 来 试 试 吧 首 先 获 取 一 个 已 经 存 在 的 仓 库, 并 将 其 分 成 两 个 仓 库, 一 个 是 最 近 的 仓 库, 一 个 是 历 史 版 本 的 仓 库, 然 后 我 们 将 看 到 如 何 在 不 更 改 仓 库 SHA 值 的 情 况 下 通 过 replace 命 令 来 合 并 他 们 我 们 将 使 用 一 个 拥 有 5 个 提 交 的 简 单 仓 库 : $ git log --oneline ef989d8 fifth commit c6e1e95 fourth commit 9c68fdc third commit c second commit c1822cf first commit 我 们 想 将 其 分 成 拆 分 成 两 条 历 史 第 一 个 到 第 四 个 提 交 的 作 为 第 一 个 历 史 版 本 第 四 第 五 个 提 交 的 作 为 最 近 的 第 二 个 历 史 版 本

316 创 建 历 史 版 本 的 历 史 很 容 易, 我 们 可 以 只 将 一 个 历 史 中 的 分 支 推 送 到 一 个 新 的 远 程 仓 库 的 master 分 支 $ git branch history c6e1e95 $ git log --oneline --decorate ef989d8 (HEAD, master) fifth commit c6e1e95 (history) fourth commit 9c68fdc third commit c second commit c1822cf first commit

317 现 在 我 们 可 以 把 这 个 新 的 history 分 支 推 送 到 我 们 新 仓 库 的 master 分 支 :

318 $ git remote add project-history $ git push project-history history:master Counting objects: 12, done. Delta compression using up to 2 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (12/12), 907 bytes, done. Total 12 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (12/12), done. To git@github.com:schacon/project-history.git * [new branch] history -> master 这 样 一 来, 我 们 的 历 史 版 本 就 发 布 了 稍 难 的 部 分 则 是 删 减 我 们 最 近 的 历 史 来 让 它 变 得 更 小 我 们 需 要 一 个 重 叠 以 便 于 用 一 个 相 等 的 提 交 来 替 换 另 一 个 提 交, 这 样 一 来, 我 们 将 截 断 到 第 四 五 个 提 交 $ git log --oneline --decorate ef989d8 (HEAD, master) fifth commit c6e1e95 (history) fourth commit 9c68fdc third commit c second commit c1822cf first commit 在 这 种 情 况 下, 创 建 一 个 能 够 指 导 扩 展 历 史 的 基 础 提 交 是 很 有 用 的 这 样 一 来, 如 果 其 他 的 开 发 者 想 要 修 改 第 一 次 提 交 或 者 其 他 操 作 时 就 知 道 要 做 些 什 么, 因 此, 接 下 来 我 们 要 做 的 是 用 命 令 创 建 一 个 最 初 的 提 交 对 象, 然 后 将 剩 下 的 提 交 ( 第 四 第 五 个 提 交 ) 变 基 到 它 的 上 面 为 了 这 么 做, 我 们 需 要 选 择 一 个 点 去 拆 分, 对 于 我 们 而 言 是 第 三 个 提 交 (SHA 是 9c68fdc) 因 此 我 们 的 提 交 将 基 于 此 提 交 树 我 们 可 以 使 用 commit-tree 命 令 来 创 建 基 础 提 交, 这 样 我 们 就 有 了 一 个 树, 并 返 回 一 个 全 新 的 无 父 节 点 的 SHA 提 交 对 象 $ echo 'get history from blah blah blah' git commit-tree 9c68fdc^{tree} 622e88e9cbfbacfb75b b9fb38dfea10cf NOTE commit-tree 命 令 属 于 底 层 指 令 有 许 多 指 令 并 非 直 接 使 用, 而 是 被 其 他 的 Git 命 令 用 来 做 更 小 一 些 的 工 作 有 时 当 我 们 做 一 些 像 这 样 的 奇 怪 事 情 时, 它 们 允 许 我 们 做 一 些 不 适 用 于 日 常 使 用 但 真 正 底 层 的 东 西 更 多 关 于 底 层 命 令 的 内 容 请 参 见 底 层 命 令 和 高 层 命 令

319 现 在 我 们 已 经 有 一 个 基 础 提 交 了, 我 们 可 以 通 过 git rebase --onto 命 令 来 将 剩 余 的 历 史 变 基 到 基 础 提 交 之 上 --onto 参 数 是 刚 才 commit-tree 命 令 返 回 的 SHA 值, 变 基 点 会 成 为 第 三 个 提 交 ( 我 们 想 留 下 的 第 一 个 提 交 的 父 提 交,9c68fdc): $ git rebase --onto 622e88 9c68fdc First, rewinding head to replay your work on top of it... Applying: fourth commit Applying: fifth commit

320 我 们 已 经 用 基 础 提 交 重 写 了 最 近 的 历 史, 基 础 提 交 包 括 如 何 重 新 组 成 整 个 历 史 的 说 明 我 们 可 以 将 新 历 史 推 送 到 新 项 目 中, 当 其 他 人 克 隆 这 个 仓 库 时, 他 们 仅 能 看 到 最 近 两 次 提 交 以 及 一 个 包 含 上 述 说 明 的 基 础 提 交 现 在 我 们 将 以 想 获 得 整 个 历 史 的 人 的 身 份 来 初 次 克 隆 这 个 项 目 在 克 隆 这 个 截 断 后 的 仓 库 后 为 了 得 到 历 史 数 据, 需 要 添 加 第 二 个 远 程 的 历 史 版 本 库 并 对 其 做 获 取 操 作 :

321 $ git clone $ cd project $ git log --oneline master e146b5f fifth commit 81a708d fourth commit 622e88e get history from blah blah blah $ git remote add project-history $ git fetch project-history From * [new branch] master -> project-history/master 现 在, 协 作 者 在 master 分 支 中 拥 有 他 们 最 近 的 提 交 并 且 在 project-history/master 分 支 中 拥 有 过 去 的 提 交 $ git log --oneline master e146b5f fifth commit 81a708d fourth commit 622e88e get history from blah blah blah $ git log --oneline project-history/master c6e1e95 fourth commit 9c68fdc third commit c second commit c1822cf first commit 为 了 合 并 它 们, 你 可 以 使 用 git replace 命 令 加 上 你 想 替 换 的 提 交 信 息 来 进 行 替 换 这 样 一 来, 我 们 就 可 以 将 master 分 支 中 的 第 四 个 提 交 替 换 为 project-history/master 分 支 中 的 第 四 个 提 交 $ git replace 81a708d c6e1e95 现 在, 查 看 master 分 支 中 的 历 史 信 息, 显 示 如 下 : $ git log --oneline master e146b5f fifth commit 81a708d fourth commit 9c68fdc third commit c second commit c1822cf first commit

322 很 酷, 是 不 是? 不 用 改 变 上 游 的 SHA-1 我 们 就 能 用 一 个 提 交 来 替 换 历 史 中 的 所 有 不 同 的 提 交, 并 且 所 有 的 工 具 (bisect,blame 等 ) 也 都 奏 效 有 趣 的 是, 即 使 是 使 用 了 c6e1e95 提 交 数 据 来 进 行 替 换, 它 的 SHA-1 仍 显 示 为 81a708d 即 使 你 运 行 了 catfile 命 令, 它 仍 会 显 示 你 替 换 的 数 据 : $ git cat-file -p 81a708d tree 7bc544cf438903b65ca9104a1e30345eee6c083d parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252 author Scott Chacon <schacon@gmail.com> committer Scott Chacon <schacon@gmail.com> fourth commit 请 记 住,81a708d 真 正 的 父 提 交 是 622e882 占 位 提 交, 而 非 呈 现 的 9c68fdce 提 交

323 另 一 个 有 趣 的 事 情 是 数 据 将 会 以 以 下 引 用 显 示 : $ git for-each-ref e146b5f14e79d c0e83fb9ebe526b8da0d commit refs/heads/master c6e1e95051d41771a649f f8809d1a74d4 commit refs/remotes/history/master e146b5f14e79d c0e83fb9ebe526b8da0d commit refs/remotes/origin/head e146b5f14e79d c0e83fb9ebe526b8da0d commit refs/remotes/origin/master c6e1e95051d41771a649f f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a bc 这 意 味 着 我 们 可 以 轻 而 易 举 的 和 其 他 人 分 享 替 换, 因 为 我 们 可 以 将 替 换 推 送 到 服 务 器 中 并 且 其 他 人 可 以 轻 松 地 下 载 也 许 在 历 史 移 植 情 况 下 不 是 很 有 用 ( 既 然 每 个 人 都 乐 意 下 载 最 新 版 本 和 历 史 版 本, 为 何 还 要 拆 分 他 们 呢?), 但 在 其 他 情 况 下 仍 然 很 有 用 凭 证 存 储 如 果 你 使 用 的 是 SSH 方 式 连 接 远 端, 并 且 设 置 了 一 个 没 有 口 令 的 密 钥, 这 样 就 可 以 在 不 输 入 用 户 名 和 密 码 的 情 况 下 安 全 地 传 输 数 据 然 而, 这 对 HTTP 协 议 来 说 是 不 可 能 的 每 一 个 连 接 都 是 需 要 用 户 名 和 密 码 的 这 在 使 用 双 重 认 证 的 情 况 下 会 更 麻 烦, 因 为 你 需 要 输 入 一 个 随 机 生 成 并 且 毫 无 规 律 的 token 作 为 密 码 幸 运 的 是,Git 拥 有 一 个 凭 证 系 统 来 处 理 这 个 事 情 下 面 有 一 些 Git 的 选 项 : 默 认 所 有 都 不 缓 存 每 一 次 连 接 都 会 询 问 你 的 用 户 名 和 密 码 cache 模 式 会 将 凭 证 存 放 在 内 存 中 一 段 时 间 密 码 永 远 不 会 被 存 储 在 磁 盘 中, 并 且 在 15 分 钟 后 从 内 存 中 清 除 store 模 式 会 将 凭 证 用 明 文 的 形 式 存 放 在 磁 盘 中, 并 且 永 不 过 期 这 意 味 着 除 非 你 修 改 了 你 在 Git 服 务 器 上 的 密 码, 否 则 你 永 远 不 需 要 再 次 输 入 你 的 凭 证 信 息 这 种 方 式 的 缺 点 是 你 的 密 码 是 用 明 文 的 方 式 存 放 在 你 的 home 目 录 下 如 果 你 使 用 的 是 Mac,Git 还 有 一 种 osxkeychain 模 式, 它 会 将 凭 证 缓 存 到 你 系 统 用 户 的 钥 匙 串 中 这 种 方 式 将 凭 证 存 放 在 磁 盘 中, 并 且 永 不 过 期, 但 是 是 被 加 密 的, 这 种 加 密 方 式 与 存 放 HTTPS 凭 证 以 及 Safari 的 自 动 填 写 是 相 同 的 如 果 你 使 用 的 是 Windows, 你 可 以 安 装 一 个 叫 做 winstore 的 辅 助 工 具 这 和 上 面 说 的 osxkeychain 十 分 类 似, 但 是 是 使 用 Windows Credential Store 来 控 制 敏 感 信 息 可 以 在 下 载 你 可 以 设 置 Git 的 配 置 来 选 择 上 述 的 一 种 方 式 $ git config --global credential.helper cache 部 分 辅 助 工 具 有 一 些 选 项 store 模 式 可 以 接 受 一 个 --file <path> 参 数, 可 以 自 定 义 存 放 密 码 的 文 件 路 径 ( 默 认 是 `~/.git-credentials`) cache 模 式 有 --timeout <seconds> 参 数, 可 以 设 置 后 台 进 程 的 存

324 活 时 间 ( 默 认 是 900, 也 就 是 15 分 钟 ) 下 面 是 一 个 配 置 store 模 式 自 定 义 路 径 的 例 子 : $ git config --global credential.helper store --file ~/.my-credentials Git 甚 至 允 许 你 配 置 多 个 辅 助 工 具 当 查 找 特 定 服 务 器 的 凭 证 时,Git 会 按 顺 序 查 询, 并 且 在 找 到 第 一 个 回 答 时 停 止 查 询 当 保 存 凭 证 时,Git 会 将 用 户 名 和 密 码 发 送 给 所 有 配 置 列 表 中 的 辅 助 工 具, 它 们 会 按 自 己 的 方 式 处 理 用 户 名 和 密 码 如 果 你 在 闪 存 上 有 一 个 凭 证 文 件, 但 又 希 望 在 该 闪 存 被 拔 出 的 情 况 下 使 用 内 存 缓 存 来 保 存 用 户 名 密 码,.gitconfig 配 置 文 件 如 下 : [credential] helper = store --file /mnt/thumbdrive/.git-credentials helper = cache --timeout 底 层 实 现 这 些 是 如 何 实 现 的 呢?Git 凭 证 辅 助 工 具 系 统 的 命 令 是 git credential, 这 个 命 令 接 收 一 个 参 数, 并 通 过 标 准 输 入 获 取 更 多 的 参 数 举 一 个 例 子 更 容 易 理 解 我 们 假 设 已 经 配 置 好 一 个 凭 证 辅 助 工 具, 这 个 辅 助 工 具 保 存 了 mygithost 的 凭 证 信 息 下 面 是 一 个 使 用 fill 命 令 的 会 话, 当 Git 尝 试 寻 找 一 个 服 务 器 的 凭 证 时 就 会 被 调 用 $ git credential fill 1 protocol=https 2 host=mygithost 3 protocol=https 4 host=mygithost username=bob password=s3cre7 $ git credential fill 5 protocol=https host=unknownhost Username for ' bob Password for ' protocol=https host=unknownhost username=bob password=s3cre7 1 这 是 开 始 交 互 的 命 令 2 Git-credential 接 下 来 会 等 待 标 准 输 入 我 们 提 供 我 们 所 知 道 的 信 息 : 协 议 和 主 机 名

325 3 一 个 空 行 代 表 输 入 已 经 完 成, 凭 证 系 统 应 该 输 出 它 所 知 道 的 信 息 4 接 下 来 由 Git-credential 接 管, 并 且 将 找 到 的 信 息 打 印 到 标 准 输 出 5 如 果 没 有 找 到 对 应 的 凭 证,Git 会 询 问 用 户 的 用 户 名 和 密 码, 我 们 将 这 些 信 息 输 入 到 在 标 准 输 出 的 地 方 ( 这 个 例 子 中 是 同 一 个 控 制 台 ) 凭 证 系 统 实 际 调 用 的 程 序 和 Git 本 身 是 分 开 的 ; 具 体 是 哪 一 个 以 及 如 何 调 用 与 credential.helper 配 置 的 值 有 关 这 个 配 置 有 多 种 格 式 : 配 置 值 foo foo -a --opt=bcd /absolute/path/foo -xyz!f() { echo "password=s3cre7"; }; f 行 为 执 行 git-credential-foo 执 行 git-credential-foo -a --opt=bcd 执 行 /absolute/path/foo -xyz! 后 面 的 代 码 会 在 shell 执 行 上 面 描 述 的 辅 助 工 具 可 以 被 称 做 git-credential-cache git-credential-store 之 类, 我 们 可 以 配 置 它 们 来 接 受 命 令 行 参 数 通 常 的 格 式 是 git-credential-foo [args] <action>. 标 准 输 入 / 输 出 协 议 和 gitcredential 一 样, 但 它 们 使 用 的 是 一 套 稍 微 不 太 一 样 的 行 为 : get 是 请 求 输 入 一 对 用 户 名 和 密 码 store 是 请 求 保 存 一 个 凭 证 到 辅 助 工 具 的 内 存 erase 会 将 给 定 的 证 书 从 辅 助 工 具 内 存 中 清 除 对 于 store 和 erase 两 个 行 为 是 不 需 要 返 回 数 据 的 (Git 也 会 忽 略 掉 ) 然 而 对 于 get,git 对 辅 助 工 具 的 返 回 信 息 十 分 感 兴 趣 如 果 辅 助 工 具 没 有 任 何 有 用 的 信 息, 它 可 以 直 接 退 出 而 不 需 要 输 出 任 何 东 西, 但 如 果 它 有 这 些 信 息, 它 在 提 供 的 信 息 后 面 增 加 它 所 拥 有 的 信 息 这 些 输 出 会 被 视 为 一 系 列 的 赋 值 语 句 ; 每 一 个 提 供 的 数 据 都 会 将 Git 已 有 的 数 据 替 换 掉 这 有 一 个 和 上 面 一 样 的 例 子, 但 是 跳 过 了 git-credential 这 一 步, 直 接 到 git-credential-store: $ git credential-store --file ~/git.store store 1 protocol=https host=mygithost username=bob password=s3cre7 $ git credential-store --file ~/git.store get 2 protocol=https host=mygithost username=bob 3 password=s3cre7

326 1 我 们 告 诉 git-credential-store 去 保 存 凭 证 : 当 访 问 时 使 用 用 户 名 bob, 密 码 是 s3cre7 2 现 在 我 们 取 出 这 个 凭 证 我 们 提 供 连 接 这 部 分 的 信 息 ( 以 及 一 个 空 行 3 git-credential-store 输 出 我 们 之 前 保 存 的 用 户 名 和 密 码 ~/git.store 文 件 的 内 容 类 似 : 仅 仅 是 一 系 列 包 含 凭 证 信 息 URL 组 成 的 行 osxkeychain 和 winstore 辅 助 工 具 使 用 它 们 后 端 存 储 的 原 生 格 式, 而 cache 使 用 它 的 内 存 格 式 ( 其 他 进 程 无 法 读 取 ) 自 定 义 凭 证 缓 存 已 经 知 道 git-credential-store 之 类 的 是 和 Git 是 相 互 独 立 的 程 序, 就 不 难 理 解 Git 凭 证 辅 助 工 具 可 以 是 任 意 程 序 虽 然 Git 提 供 的 辅 助 工 具 覆 盖 了 大 多 数 常 见 的 使 用 场 景, 但 并 不 能 满 足 所 有 情 况 比 如, 假 设 你 的 整 个 团 队 共 享 一 些 凭 证, 也 许 是 在 部 署 时 使 用 这 些 凭 证 是 保 存 在 一 个 共 享 目 录 里, 由 于 这 些 凭 证 经 常 变 更, 所 以 你 不 想 把 它 们 复 制 到 你 自 己 的 凭 证 仓 库 中 现 有 的 辅 助 工 具 无 法 满 足 这 种 情 况 ; 来 看 看 我 们 如 何 自 己 实 现 一 个 这 个 程 序 应 该 拥 有 几 个 核 心 功 能 : 1. 我 们 唯 一 需 要 关 注 的 行 为 是 get;store 和 erase 是 写 操 作, 所 以 当 接 受 到 这 两 个 请 求 时 我 们 直 接 退 出 即 可 2. 共 享 的 凭 证 文 件 格 式 和 git-credential-store 使 用 的 格 式 相 同 3. 凭 证 文 件 的 路 径 一 般 是 固 定 的, 但 我 们 应 该 允 许 用 户 传 入 一 个 自 定 义 路 径 以 防 万 一 我 们 再 一 次 使 用 Ruby 来 编 写 这 个 扩 展, 但 只 要 Git 能 够 执 行 最 终 的 程 序, 任 何 语 言 都 是 可 以 的 这 是 我 们 的 凭 证 辅 助 工 具 的 完 整 代 码 :

327 #!/usr/bin/env ruby require 'optparse' path = File.expand_path '~/.git-credentials' 1 OptionParser.new do opts opts.banner = 'USAGE: git-credential-read-only [options] <action>' opts.on('-f', '--file PATH', 'Specify path for backing store') do argpath path = File.expand_path argpath end end.parse! exit(0) unless ARGV[0].downcase == 'get' 2 exit(0) unless File.exists? path known = {} 3 while line = STDIN.gets break if line.strip == '' k,v = line.strip.split '=', 2 known[k] = v end File.readlines(path).each do fileline 4 prot,user,pass,host = fileline.scan(/^(.*?):\/\/(.*?):(.*?)@(.*)$/).first if prot == known['protocol'] and host == known['host'] then puts "protocol=#{prot}" puts "host=#{host}" puts "username=#{user}" puts "password=#{pass}" exit(0) end end 1 我 们 在 这 里 解 析 命 令 行 参 数, 允 许 用 户 指 定 输 入 文 件, 默 认 是 ~/.git-credentials. 2 这 个 程 序 只 有 在 接 受 到 get 行 为 的 请 求 并 且 后 端 存 储 的 文 件 存 在 时 才 会 有 输 出 3 这 个 循 环 从 标 准 输 入 读 取 数 据, 直 到 读 取 到 第 一 个 空 行 输 入 的 数 据 被 保 存 到 known 哈 希 表 中, 之 后 需 要 用 到 4 这 个 循 环 读 取 存 储 文 件 中 的 内 容, 寻 找 匹 配 的 行 如 果 known 中 的 协 议 和 主 机 名 与 该 行 相 匹 配, 这 个 程 序 输 出 结 果 并 退 出 我 们 把 这 个 辅 助 工 具 保 存 为 git-credential-read-only, 放 到 我 们 的 PATH 路 径 下 并 且 给 予 执 行 权 限 一 个 交 互 式 会 话 类 似 :

328 $ git credential-read-only --file=/mnt/shared/creds get protocol=https host=mygithost protocol=https host=mygithost username=bob password=s3cre7 由 于 这 个 的 名 字 是 git- 开 头, 所 以 我 们 可 以 在 配 置 值 中 使 用 简 便 的 语 法 : $ git config --global credential.helper read-only --file /mnt/shared/creds 正 如 你 看 到 的, 扩 展 这 个 系 统 是 相 当 简 单 的, 并 且 可 以 为 你 和 你 的 团 队 解 决 一 些 常 见 问 题 总 结 你 已 经 接 触 了 很 多 能 够 精 确 地 操 控 提 交 和 暂 存 区 的 高 级 工 具 当 你 碰 到 问 题 时, 你 应 该 可 以 很 容 易 找 出 是 哪 个 分 支 在 什 么 时 候 由 谁 引 入 了 它 们 如 果 你 想 在 项 目 中 使 用 子 项 目, 你 也 已 经 知 道 如 何 来 满 足 这 些 需 求 到 此, 你 应 该 能 毫 无 压 力 地 在 命 令 行 中 使 用 Git 来 完 成 日 常 中 的 大 部 分 事 情

329 自 定 义 Git 到 目 前 为 止, 我 们 已 经 阐 述 了 Git 基 本 的 运 作 机 制 和 使 用 方 式, 介 绍 了 许 多 Git 提 供 的 工 具 来 帮 助 你 简 单 且 有 效 地 使 用 它 在 本 章, 我 们 将 演 示 如 何 借 助 Git 的 一 些 重 要 的 配 置 方 法 和 钩 子 机 制, 来 满 足 自 定 义 的 需 求 通 过 这 些 工 具, 它 会 和 你 你 的 公 司 或 你 的 团 队 配 合 得 天 衣 无 缝 配 置 Git 你 在 起 步 中 看 到, 可 以 用 git config 配 置 Git 首 先 要 做 的 事 情 就 是 设 置 你 的 名 字 和 邮 件 地 址 : $ git config --global user.name "John Doe" $ git config --global user. johndoe@example.com 现 在, 你 会 了 解 到 许 多 更 有 趣 的 选 项, 并 用 类 似 的 方 式 来 定 制 Git 首 先, 快 速 回 忆 下 :Git 使 用 一 系 列 配 置 文 件 来 保 存 你 自 定 义 的 行 为 它 首 先 会 查 找 /etc/gitconfig 文 件, 该 文 件 含 有 系 统 里 每 位 用 户 及 他 们 所 拥 有 的 仓 库 的 配 置 值 如 果 你 传 递 --system 选 项 给 git config, 它 就 会 读 写 该 文 件 接 下 来 Git 会 查 找 每 个 用 户 的 ~/.gitconfig 文 件 ( 或 者 ~/.config/git/config 文 件 ) 你 可 以 传 递 --global 选 项 让 Git 读 写 该 文 件 最 后 Git 会 查 找 你 正 在 操 作 的 版 本 库 所 对 应 的 Git 目 录 下 的 配 置 文 件 (.git/config) 这 个 文 件 中 的 值 只 对 该 版 本 库 有 效 以 上 三 个 层 次 中 每 层 的 配 置 ( 系 统 全 局 本 地 ) 都 会 覆 盖 掉 上 一 层 次 的 配 置, 所 以.git/config 中 的 值 会 覆 盖 掉 /etc/gitconfig 中 所 对 应 的 值 NOTE Git 的 配 置 文 件 是 纯 文 本 的, 所 以 你 可 以 直 接 手 动 编 辑 这 些 配 置 文 件, 输 入 合 乎 语 法 的 值 但 是 运 行 git config 命 令 会 更 简 单 些 客 户 端 基 本 配 置 Git 能 够 识 别 的 配 置 项 分 为 两 大 类 : 客 户 端 和 服 务 器 端 其 中 大 部 分 属 于 客 户 端 配 置 可 以 依 你 个 人 的 工 作 偏 好 进 行 配 置 尽 管 Git 支 持 的 选 项 繁 多, 但 其 中 大 部 分 仅 仅 在 某 些 罕 见 的 情 况 下 有 意 义 我 们 只 讲 述 最 平 常 和 最 有 用 的 选 项 如 果 想 得 到 你 当 前 版 本 的 Git 支 持 的 选 项 列 表, 请 运 行 $ man git-config 这 个 命 令 列 出 了 所 有 可 用 的 选 项, 以 及 与 之 相 关 的 介 绍 你 也 可 以 在 找 到 同 样 的 内 容

330 core.editor 默 认 情 况 下,Git 会 调 用 环 境 变 量 ($VISUAL 或 $EDITOR) 设 置 的 任 意 文 本 编 辑 器, 如 果 没 有 设 置, 会 调 用 vi 来 创 建 和 编 辑 你 的 提 交 以 及 标 签 信 息 你 可 以 使 用 core.editor 选 项 来 修 改 默 认 的 编 辑 器 : $ git config --global core.editor emacs 现 在, 无 论 你 定 义 了 什 么 终 端 编 辑 器,Git 都 会 调 用 Emacs 编 辑 信 息 commit.template 如 果 把 此 项 指 定 为 你 的 系 统 上 某 个 文 件 的 路 径, 当 你 提 交 的 时 候, Git 会 使 用 该 文 件 的 内 容 作 为 提 交 的 默 认 信 息 例 如 : 假 设 你 创 建 了 一 个 叫 ~/.gitmessage.txt 的 模 板 文 件, 类 似 这 样 : subject line what happened [ticket: X] 要 想 让 Git 把 它 作 为 运 行 git commit 时 显 示 在 你 的 编 辑 器 中 的 默 认 信 息, 如 下 设 置 commit.template: $ git config --global commit.template ~/.gitmessage.txt $ git commit 然 后 当 你 提 交 时, 编 辑 器 中 就 会 显 示 如 下 的 提 交 信 息 占 位 符 :

331 subject line what happened [ticket: X] # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: lib/test.rb # ~ ~ ".git/commit_editmsg" 14L, 297C 如 果 你 的 团 队 对 提 交 信 息 有 格 式 要 求, 可 以 在 系 统 上 创 建 一 个 文 件, 并 配 置 Git 把 它 作 为 默 认 的 模 板, 这 样 可 以 更 加 容 易 地 使 提 交 信 息 遵 循 格 式 core.pager 该 配 置 项 指 定 Git 运 行 诸 如 log 和 diff 等 命 令 所 使 用 的 分 页 器 你 可 以 把 它 设 置 成 用 more 或 者 任 何 你 喜 欢 的 分 页 器 ( 默 认 用 的 是 less), 当 然 也 可 以 设 置 成 空 字 符 串, 关 闭 该 选 项 : $ git config --global core.pager '' 这 样 不 管 命 令 的 输 出 量 多 少,Git 都 会 在 一 页 显 示 所 有 内 容 user.signingkey 如 果 你 要 创 建 经 签 署 的 含 附 注 的 标 签 ( 正 如 签 署 工 作 所 述 ), 那 么 把 你 的 GPG 签 署 密 钥 设 置 为 配 置 项 会 更 好 如 下 设 置 你 的 密 钥 ID: $ git config --global user.signingkey <gpg-key-id> 现 在, 你 每 次 运 行 git tag 命 令 时, 即 可 直 接 签 署 标 签, 而 无 需 定 义 密 钥 : $ git tag -s <tag-name>

332 core.excludesfile 正 如 忽 略 文 件 所 述, 你 可 以 在 你 的 项 目 的.gitignore 文 件 里 面 规 定 无 需 纳 入 Git 管 理 的 文 件 的 模 板, 这 样 它 们 既 不 会 出 现 在 未 跟 踪 列 表, 也 不 会 在 你 运 行 git add 后 被 暂 存 不 过 有 些 时 候, 你 想 要 在 你 所 有 的 版 本 库 中 忽 略 掉 某 一 类 文 件 如 果 你 的 操 作 系 统 是 OS X, 很 可 能 就 是 指.DS_Store 如 果 你 把 Emacs 或 Vim 作 为 首 选 的 编 辑 器, 你 肯 定 知 道 以 ~ 结 尾 的 临 时 文 件 这 个 配 置 允 许 你 设 置 类 似 于 全 局 生 效 的.gitignore 文 件 如 果 你 按 照 下 面 的 内 容 创 建 一 个 ~/.gitignore_global 文 件 : *~.DS_Store 然 后 运 行 git config --global core.excludesfile ~/.gitignore_global,git 将 把 那 些 文 件 永 远 地 拒 之 门 外 help.autocorrect 假 如 你 打 错 了 一 条 命 令, 会 显 示 : $ git chekcout master git:'chekcout' 不 是 一 个 git 命 令 参 见 'git --help' 您 指 的 是 这 个 么? checkout Git 会 尝 试 猜 测 你 的 意 图, 但 是 它 不 会 越 俎 代 庖 如 果 你 把 help.autocorrect 设 置 成 1, 那 么 只 要 有 一 个 命 令 被 模 糊 匹 配 到 了,Git 会 自 动 运 行 该 命 令 $ git chekcout master 警 告 : 您 运 行 一 个 不 存 在 的 Git 命 令 'chekcout' 继 续 执 行 假 定 您 要 要 运 行 的 是 'checkout' 在 0.1 秒 钟 后 自 动 运 行... 注 意 提 示 信 息 中 的 0.1 秒 help.autocorrect 接 受 一 个 代 表 十 分 之 一 秒 的 整 数 所 以 如 果 你 把 它 设 置 为 50, Git 将 在 自 动 执 行 命 令 前 给 你 5 秒 的 时 间 改 变 主 意 Git 中 的 着 色 Git 充 分 支 持 对 终 端 内 容 着 色, 对 你 凭 肉 眼 简 单 快 速 分 析 命 令 输 出 有 很 大 帮 助 你 可 以 设 置 许 多 的 相 关 选 项 来 满 足 自 己 的 偏 好

333 color.ui Git 会 自 动 着 色 大 部 分 输 出 内 容, 但 如 果 你 不 喜 欢 花 花 绿 绿, 也 可 以 关 掉 要 想 关 掉 Git 的 终 端 颜 色 输 出, 试 一 下 这 个 : $ git config --global color.ui false 这 个 设 置 的 默 认 值 是 auto, 它 会 着 色 直 接 输 出 到 终 端 的 内 容 ; 而 当 内 容 被 重 定 向 到 一 个 管 道 或 文 件 时, 则 忽 略 着 色 功 能 你 也 可 以 设 置 成 always, 来 忽 略 掉 管 道 和 终 端 的 不 同, 即 在 任 何 情 况 下 着 色 输 出 你 很 少 会 这 么 设 置, 在 大 多 数 场 合 下, 如 果 你 想 在 被 重 定 向 的 输 出 中 插 入 颜 色 码, 可 以 传 递 --color 标 志 给 Git 命 令 来 强 制 它 这 么 做 默 认 设 置 就 已 经 能 满 足 大 多 数 情 况 下 的 需 求 了 color.* 要 想 具 体 到 哪 些 命 令 输 出 需 要 被 着 色 以 及 怎 样 着 色, 你 需 要 用 到 和 具 体 命 令 有 关 的 颜 色 配 置 选 项 它 们 都 能 被 置 为 true false 或 always: color.branch color.diff color.interactive color.status 另 外, 以 上 每 个 配 置 项 都 有 子 选 项, 它 们 可 以 被 用 来 覆 盖 其 父 设 置, 以 达 到 为 输 出 的 各 个 部 分 着 色 的 目 的 例 如, 为 了 让 diff 的 输 出 信 息 以 蓝 色 前 景 黑 色 背 景 和 粗 体 显 示, 你 可 以 运 行 $ git config --global color.diff.meta "blue black bold" 你 能 设 置 的 颜 色 有 :normal black red green yellow blue magenta cyan 或 white 正 如 以 上 例 子 设 置 的 粗 体 属 性, 想 要 设 置 字 体 属 性 的 话, 可 以 选 择 包 括 :bold dim ul( 下 划 线 ) blink reverse( 交 换 前 景 色 和 背 景 色 ) 外 部 的 合 并 与 比 较 工 具 虽 然 Git 自 己 内 置 了 一 个 diff 实 现, 而 且 到 目 前 为 止 我 们 一 直 在 使 用 它, 但 你 能 够 用 一 个 外 部 的 工 具 替 代 它 除 此 以 外, 你 还 能 设 置 一 个 图 形 化 的 工 具 来 合 并 和 解 决 冲 突, 从 而 不 必 自 己 手 动 解 决 这 里 我 们 以 一 个 不 错 且 免 费 的 工 具 Perforce 图 形 化 合 并 工 具 (P4Merge) 来 展 示 如 何 用 一 个 外 部 的 工 具 来 合 并 和 解 决 冲 突 P4Merge 可 以 在 所 有 主 流 平 台 上 运 行, 所 以 安 装 上 应 该 没 有 什 么 困 难 在 这 个 例 子 中, 我 们 使 用 的 路 径 名 可 以 直 接 应 用 在 Mac 和 Linux 上 ; 在 Windows 上,/usr/local/bin 需 要 被 改 为 你 的 环 境 中 可 执 行 文 件 所 在 的 目 录 路 径

334 首 先, 从 下 载 P4Merge 接 下 来, 你 要 编 写 一 个 全 局 包 装 脚 本 来 运 行 你 的 命 令 我 们 会 使 用 Mac 上 的 路 径 来 指 定 该 脚 本 的 位 置, 在 其 他 系 统 上, 它 将 是 p4merge 二 进 制 文 件 所 在 的 目 录 创 建 一 个 名 为 extmerge 的 脚 本 包 装 merge 命 令, 让 它 把 参 数 转 发 给 p4merge 二 进 制 文 件 : $ cat /usr/local/bin/extmerge #!/bin/sh /Applications/p4merge.app/Contents/MacOS/p4merge $* 包 装 diff 命 令 的 脚 本 首 先 确 保 传 递 了 七 个 参 数 过 来, 随 后 把 其 中 两 个 转 发 给 包 装 了 merge 的 脚 本 默 认 情 况 下, Git 传 递 以 下 参 数 给 diff: path old-file old-hex old-mode new-file new-hex new-mode 由 于 你 仅 仅 需 要 old-file 和 new-file 参 数, 由 包 装 diff 的 脚 本 来 转 发 它 们 吧 $ cat /usr/local/bin/extdiff #!/bin/sh [ $# -eq 7 ] && /usr/local/bin/extmerge "$2" "$5" 你 也 需 要 确 保 这 些 脚 本 具 有 可 执 行 权 限 : $ sudo chmod +x /usr/local/bin/extmerge $ sudo chmod +x /usr/local/bin/extdiff 现 在 你 可 以 修 改 配 置 文 件 来 使 用 你 自 定 义 的 合 并 和 比 较 工 具 了 这 将 涉 及 许 多 自 定 义 设 置 :merge.tool 通 知 Git 该 使 用 哪 个 合 并 工 具, mergetool.<tool>.cmd 规 定 命 令 运 行 的 方 式,mergetool.<tool>.trustExitCode 会 通 知 Git 程 序 的 返 回 值 是 否 表 示 合 并 操 作 成 功,diff.external 通 知 Git 该 用 什 么 命 令 做 比 较 因 此, 你 可 以 运 行 以 下 四 条 配 置 命 令 : $ git config --global merge.tool extmerge $ git config --global mergetool.extmerge.cmd \ 'extmerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"' $ git config --global mergetool.extmerge.trustexitcode false $ git config --global diff.external extdiff 或 编 辑 你 的 ~/.gitconfig 文 件, 添 加 以 下 各 行 :

335 [merge] tool = extmerge [mergetool "extmerge"] cmd = extmerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED" trustexitcode = false [diff] external = extdiff 待 一 切 设 置 妥 当 后, 如 果 你 像 这 样 运 行 diff 命 令 : $ git diff 32d1776b1^ 32d1776b1 Git 将 启 动 P4Merge, 而 不 是 在 命 令 行 输 出 比 较 的 结 果, 就 像 这 样 : Figure 143. P4Merge. 如 果 你 尝 试 合 并 两 个 分 支, 随 后 遇 到 了 合 并 冲 突, 运 行 git mergetool,git 会 调 用 P4Merge 让 你 通 过 图 形 界 面 来 解 决 冲 突 设 置 包 装 脚 本 的 好 处 在 于 大 大 降 低 了 改 变 diff 和 merge 工 具 的 工 作 量 举 个 例 子, 想 把 extdiff 和 extmerge 的 工 具 改 成 KDiff3, 你 要 做 的 仅 仅 是 编 辑 extmerge 脚 本 文 件 :

336 $ cat /usr/local/bin/extmerge #!/bin/sh /Applications/kdiff3.app/Contents/MacOS/kdiff3 $* 现 在,Git 将 使 用 KDiff3 作 为 查 看 比 较 和 解 决 合 并 冲 突 的 工 具 Git 预 设 了 许 多 其 他 的 合 并 和 解 决 冲 突 的 工 具, 无 需 特 别 的 设 置 你 就 能 用 上 它 们 要 想 看 到 它 支 持 的 工 具 列 表, 试 一 下 这 个 : $ git mergetool --tool-help 'git mergetool --tool=<tool>' may be set to one of the following: emerge gvimdiff gvimdiff2 opendiff p4merge vimdiff vimdiff2 The following tools are valid, but not currently available: araxis bc3 codecompare deltawalker diffmerge diffuse ecmerge kdiff3 meld tkdiff tortoisemerge xxdiff Some of the tools listed above only work in a windowed environment. If run in a terminal-only session, they will fail. 如 果 你 不 想 用 到 KDiff3 的 所 有 功 能, 只 是 想 用 它 来 合 并, 那 么 kdiff3 正 符 合 你 的 要 求, 运 行 : $ git config --global merge.tool kdiff3 如 果 运 行 了 以 上 命 令, 而 没 有 设 置 extmerge 和 extdiff 文 件,Git 会 用 KDiff3 做 合 并, 让 内 置 的 diff 来 做 比 较

337 格 式 化 与 多 余 的 空 白 字 符 格 式 化 与 多 余 的 空 白 字 符 是 许 多 开 发 人 员 在 协 作 时, 特 别 是 在 跨 平 台 情 况 下, 不 时 会 遇 到 的 令 人 头 疼 的 琐 碎 的 问 题 由 于 编 辑 器 的 不 同 或 者 文 件 行 尾 的 换 行 符 在 Windows 下 被 替 换 了, 一 些 细 微 的 空 格 变 化 会 不 经 意 地 混 入 提 交 的 补 丁 或 其 它 协 作 成 果 中 不 用 怕,Git 提 供 了 一 些 配 置 项 来 帮 助 你 解 决 这 些 问 题 core.autocrlf 假 如 你 正 在 Windows 上 写 程 序, 而 你 的 同 伴 用 的 是 其 他 系 统 ( 或 相 反 ), 你 可 能 会 遇 到 CRLF 问 题 这 是 因 为 Windows 使 用 回 车 (CR) 和 换 行 (LF) 两 个 字 符 来 结 束 一 行, 而 Mac 和 Linux 只 使 用 换 行 (LF) 一 个 字 符 虽 然 这 是 小 问 题, 但 它 会 极 大 地 扰 乱 跨 平 台 协 作 许 多 Windows 上 的 编 辑 器 会 悄 悄 把 行 尾 的 换 行 字 符 转 换 成 回 车 和 换 行, 或 在 用 户 按 下 Enter 键 时, 插 入 回 车 和 换 行 两 个 字 符 Git 可 以 在 你 提 交 时 自 动 地 把 回 车 和 换 行 转 换 成 换 行, 而 在 检 出 代 码 时 把 换 行 转 换 成 回 车 和 换 行 你 可 以 用 core.autocrlf 来 打 开 此 项 功 能 如 果 是 在 Windows 系 统 上, 把 它 设 置 成 true, 这 样 在 检 出 代 码 时, 换 行 会 被 转 换 成 回 车 和 换 行 : $ git config --global core.autocrlf true 如 果 使 用 以 换 行 作 为 行 结 束 符 的 Linux 或 Mac, 你 不 需 要 Git 在 检 出 文 件 时 进 行 自 动 的 转 换 ; 然 而 当 一 个 以 回 车 加 换 行 作 为 行 结 束 符 的 文 件 不 小 心 被 引 入 时, 你 肯 定 想 让 Git 修 正 你 可 以 把 core.autocrlf 设 置 成 input 来 告 诉 Git 在 提 交 时 把 回 车 和 换 行 转 换 成 换 行, 检 出 时 不 转 换 : $ git config --global core.autocrlf input 这 样 在 Windows 上 的 检 出 文 件 中 会 保 留 回 车 和 换 行, 而 在 Mac 和 Linux 上, 以 及 版 本 库 中 会 保 留 换 行 如 果 你 是 Windows 程 序 员, 且 正 在 开 发 仅 运 行 在 Windows 上 的 项 目, 可 以 设 置 false 取 消 此 功 能, 把 回 车 保 留 在 版 本 库 中 : $ git config --global core.autocrlf false core.whitespace Git 预 先 设 置 了 一 些 选 项 来 探 测 和 修 正 多 余 空 白 字 符 问 题 它 提 供 了 六 种 处 理 多 余 空 白 字 符 的 主 要 选 项 其 中 三 个 默 认 开 启, 另 外 三 个 默 认 关 闭, 不 过 你 可 以 自 由 地 设 置 它 们 默 认 被 打 开 的 三 个 选 项 是 :blank-at-eol, 查 找 行 尾 的 空 格 ;blank-at-eof, 盯 住 文 件 底 部 的 空 行 ;space-before-tab, 警 惕 行 头 tab 前 面 的 空 格 默 认 被 关 闭 的 三 个 选 项 是 :indent-with-non-tab, 揪 出 以 空 格 而 非 tab 开 头 的 行 ( 你 可 以 用 tabwidth 选 项 控 制 它 );tab-in-indent, 监 视 在 行 头 表 示 缩 进 的 tab;cr-at-eol, 告 诉 Git 忽 略 行 尾 的 回 车

338 通 过 设 置 core.whitespace, 你 可 以 让 Git 按 照 你 的 意 图 来 打 开 或 关 闭 以 逗 号 分 割 的 选 项 要 想 关 闭 某 个 选 项, 你 可 以 在 输 入 设 置 选 项 时 不 指 定 它 或 在 它 前 面 加 个 - 例 如, 如 果 你 想 要 打 开 除 cr-at-eol 之 外 的 所 有 选 项 : $ git config --global core.whitespace \ trailing-space,space-before-tab,indent-with-non-tab 当 你 运 行 git diff 命 令 并 尝 试 给 输 出 着 色 时,Git 将 探 测 到 这 些 问 题, 因 此 你 在 提 交 前 就 能 修 复 它 们 用 git apply 打 补 丁 时 你 也 会 从 中 受 益 如 果 正 准 备 应 用 的 补 丁 存 有 特 定 的 空 白 问 题, 你 可 以 让 Git 在 应 用 补 丁 时 发 出 警 告 : $ git apply --whitespace=warn <patch> 或 者 让 Git 在 打 上 补 丁 前 自 动 修 正 此 问 题 : $ git apply --whitespace=fix <patch> 这 些 选 项 也 能 运 用 于 git rebase 如 果 提 交 了 有 空 白 问 题 的 文 件, 但 还 没 推 送 到 上 游, 你 可 以 运 行 git rebase --whitespace=fix 来 让 Git 在 重 写 补 丁 时 自 动 修 正 它 们 服 务 器 端 配 置 Git 服 务 器 端 的 配 置 项 相 对 来 说 并 不 多, 但 仍 有 一 些 饶 有 生 趣 的 选 项 值 得 你 一 看 receive.fsckobjects Git 能 够 确 认 每 个 对 象 的 有 效 性 以 及 SHA-1 检 验 和 是 否 保 持 一 致 但 Git 不 会 在 每 次 推 送 时 都 这 么 做 这 个 操 作 很 耗 时 间, 很 有 可 能 会 拖 慢 提 交 的 过 程, 特 别 是 当 库 或 推 送 的 文 件 很 大 的 情 况 下 如 果 想 在 每 次 推 送 时 都 要 求 Git 检 查 一 致 性, 设 置 receive.fsckobjects 为 true 来 强 迫 它 这 么 做 : $ git config --system receive.fsckobjects true 现 在 Git 会 在 每 次 推 送 生 效 前 检 查 库 的 完 整 性, 确 保 没 有 被 有 问 题 的 客 户 端 引 入 破 坏 性 数 据 receive.denynonfastforwards 如 果 你 变 基 已 经 被 推 送 的 提 交, 继 而 再 推 送, 又 或 者 推 送 一 个 提 交 到 远 程 分 支, 而 这 个 远 程 分 支 当 前 指 向 的 提 交 不 在 该 提 交 的 历 史 中, 这 样 的 推 送 会 被 拒 绝 这 通 常 是 个 很 好 的 策 略, 但 有 时 在 变 基 的 过 程 中, 你 确 信 自 己 需 要 更 新 远 程 分 支, 可 以 在 push 命 令 后 加 -f 标 志 来 强 制 更 新 (force-update) 要 禁 用 这 样 的 强 制 更 新 推 送 (force-pushes), 可 以 设 置 receive.denynonfastforwards:

339 $ git config --system receive.denynonfastforwards true 稍 后 我 们 会 提 到, 用 服 务 器 端 的 接 收 钩 子 也 能 达 到 同 样 的 目 的 那 种 方 法 可 以 做 到 更 细 致 的 控 制, 例 如 禁 止 某 一 类 用 户 做 非 快 进 (non-fast-forwards) 推 送 receive.denydeletes 有 一 些 方 法 可 以 绕 过 denynonfastforwards 策 略 其 中 一 种 是 先 删 除 某 个 分 支, 再 连 同 新 的 引 用 一 起 推 送 回 该 分 支 把 receive.denydeletes 设 置 为 true 可 以 把 这 个 漏 洞 补 上 : $ git config --system receive.denydeletes true 这 样 会 禁 止 通 过 推 送 删 除 分 支 和 标 签 没 有 用 户 可 以 这 么 做 要 删 除 远 程 分 支, 必 须 从 服 务 器 手 动 删 除 引 用 文 件 通 过 用 户 访 问 控 制 列 表 (ACL) 也 能 够 在 用 户 级 的 粒 度 上 实 现 同 样 的 功 能, 你 将 在 使 用 强 制 策 略 的 一 个 例 子 一 节 学 到 具 体 的 做 法 Git 属 性 你 也 可 以 针 对 特 定 的 路 径 配 置 某 些 设 置 项, 这 样 Git 就 只 对 特 定 的 子 目 录 或 子 文 件 集 运 用 它 们 这 些 基 于 路 径 的 设 置 项 被 称 为 Git 属 性, 可 以 在 你 的 目 录 下 的.gitattributes 文 件 内 进 行 设 置 ( 通 常 是 你 的 项 目 的 根 目 录 ) 如 果 不 想 让 这 些 属 性 文 件 与 其 它 文 件 一 同 提 交, 你 也 可 以 在.git/info/attributes 文 件 中 进 行 设 置 通 过 使 用 属 性, 你 可 以 对 项 目 中 的 文 件 或 目 录 单 独 定 义 不 同 的 合 并 策 略, 让 Git 知 道 怎 样 比 较 非 文 本 文 件, 或 者 让 Git 在 提 交 或 检 出 前 过 滤 内 容 在 本 节, 你 将 学 习 到 一 些 能 在 自 己 的 项 目 中 用 到 的 属 性, 并 看 到 几 个 实 际 的 例 子 二 进 制 文 件 你 可 以 用 Git 属 性 让 Git 知 道 哪 些 是 二 进 制 文 件 ( 以 防 它 没 有 识 别 出 来 ), 并 指 示 其 如 何 处 理 这 些 文 件 例 如, 一 些 文 本 文 件 是 由 机 器 产 生 的, 没 有 办 法 进 行 比 较, 但 是 一 些 二 进 制 文 件 可 以 比 较 你 将 了 解 到 怎 样 让 Git 区 分 这 些 文 件 识 别 二 进 制 文 件 有 些 文 件 表 面 上 是 文 本 文 件, 实 质 上 应 被 作 为 二 进 制 文 件 处 理 例 如,Mac 平 台 上 的 Xcode 项 目 会 包 含 一 个 以.pbxproj 结 尾 的 文 件, 它 通 常 是 一 个 记 录 项 目 构 建 配 置 等 信 息 的 JSON( 纯 文 本 Javascript 数 据 类 型 ) 数 据 集, 由 IDE 写 入 磁 盘 虽 然 技 术 上 看 它 是 由 UTF-8 编 码 的 文 本 文 件, 但 你 并 不 会 希 望 将 它 当 作 文 本 文 件 来 处 理, 因 为 它 其 实 是 一 个 轻 量 级 数 据 库 如 果 有 两 个 人 修 改 了 它, 你 通 常 无 法 合 并 内 容,diff 的 输 出 也 帮 不 上 什 么 忙 它 本 应 被 机 器 处 理 因 此, 你 想 把 它 当 成 二 进 制 文 件 要 让 Git 把 所 有 pbxproj 文 件 当 成 二 进 制 文 件, 在.gitattributes 文 件 中 如 下 设 置 :

340 *.pbxproj binary 现 在,Git 不 会 尝 试 转 换 或 修 正 回 车 换 行 (CRLF) 问 题, 当 你 在 项 目 中 运 行 git show 或 git diff 时,Git 也 不 会 比 较 或 打 印 该 文 件 的 变 化 比 较 二 进 制 文 件 你 也 可 以 使 用 Git 属 性 来 有 效 地 比 较 两 个 二 进 制 文 件 秘 诀 在 于, 告 诉 Git 怎 么 把 你 的 二 进 制 文 件 转 化 为 文 本 格 式, 从 而 能 够 使 用 普 通 的 diff 方 式 进 行 对 比 首 先, 让 我 们 尝 试 用 这 个 技 术 解 决 世 人 最 头 疼 的 问 题 之 一 : 对 Microsoft Word 文 档 进 行 版 本 控 制 大 家 都 知 道,Microsoft Word 几 乎 是 世 上 最 难 缠 的 编 辑 器, 尽 管 如 此, 大 家 还 是 在 用 它 如 果 想 对 Word 文 档 进 行 版 本 控 制, 你 可 以 把 文 件 加 入 到 Git 库 中, 每 次 修 改 后 提 交 即 可 但 这 样 做 有 什 么 实 际 意 义 呢? 毕 竟 运 行 git diff 命 令 后, 你 只 能 得 到 如 下 的 结 果 : $ git diff diff --git a/chapter1.docx b/chapter1.docx index 88839c4..4afcb7c Binary files a/chapter1.docx and b/chapter1.docx differ 除 了 检 出 之 后 睁 大 眼 睛 逐 行 扫 描, 就 真 的 没 有 办 法 直 接 比 较 两 个 不 同 版 本 的 Word 文 档 吗?Git 属 性 能 很 好 地 解 决 此 问 题 把 下 面 这 行 文 本 加 到 你 的.gitattributes 文 件 中 : *.docx diff=word 这 告 诉 Git 当 你 尝 试 查 看 包 含 变 更 的 比 较 结 果 时, 所 有 匹 配.docx 模 式 的 文 件 都 应 该 使 用 word 过 滤 器 word 过 滤 器 是 什 么? 我 们 现 在 就 来 设 置 它 我 们 会 对 Git 进 行 配 置, 令 其 能 够 借 助 docx2txt 程 序 将 Word 文 档 转 为 可 读 文 本 文 件, 这 样 不 同 的 文 件 间 就 能 够 正 确 比 较 了 首 先, 你 需 要 安 装 docx2txt; 它 可 以 从 下 载 按 照 INSTALL 文 件 的 说 明, 把 它 放 到 你 的 可 执 行 路 径 下 接 下 来, 你 还 需 要 写 一 个 脚 本 把 输 出 结 果 包 装 成 Git 支 持 的 格 式 在 你 的 可 执 行 路 径 下 创 建 一 个 叫 docx2txt 文 件, 添 加 这 些 内 容 : #!/bin/bash docx2txt.pl $1 - 别 忘 了 用 chmod a+x 给 这 个 文 件 加 上 可 执 行 权 限 最 后, 你 需 要 配 置 Git 来 使 用 这 个 脚 本 : $ git config diff.word.textconv docx2txt

341 现 在 如 果 在 两 个 快 照 之 间 进 行 比 较,Git 就 会 对 那 些 以.docx 结 尾 的 文 件 应 用 word 过 滤 器, 即 docx2txt 这 样 你 的 Word 文 件 就 能 被 高 效 地 转 换 成 文 本 文 件 并 进 行 比 较 了 作 为 例 子, 我 把 本 书 的 第 一 章 另 存 为 Word 文 件, 并 提 交 到 Git 版 本 库 接 着, 往 其 中 加 入 一 个 新 的 段 落 运 行 git diff, 输 出 如 下 : $ git diff diff --git a/chapter1.docx b/chapter1.docx index 0b013ca..ba25db a/chapter1.docx ,6 This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so About Version Control What is "version control", and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer. +Testing: 1, 2, 3. If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead Local Version Control Systems Many people's version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they're clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you're in and accidentally write to the wrong file or copy over files you don't mean to. Git 成 功 地 挑 出 了 我 们 添 加 的 那 句 话 Testing: 1, 2, 3., 一 字 不 差 还 算 不 上 完 美 格 式 上 的 变 动 显 示 不 出 来 但 已 经 足 够 了 你 还 能 用 这 个 方 法 比 较 图 像 文 件 其 中 一 个 办 法 是, 在 比 较 时 对 图 像 文 件 运 用 一 个 过 滤 器, 提 炼 出 EXIF 信 息 这 是 在 大 部 分 图 像 格 式 中 都 有 记 录 的 一 种 元 数 据 如 果 你 下 载 并 安 装 了 exiftool 程 序, 可 以 利 用 它 将 图

342 像 转 换 为 关 于 元 数 据 的 文 本 信 息, 这 样 比 较 时 至 少 能 以 文 本 的 形 式 显 示 发 生 过 的 变 动 : $ echo '*.png diff=exif' >>.gitattributes $ git config diff.exif.textconv exiftool 如 果 在 项 目 中 替 换 了 一 个 图 像 文 件, 运 行 git diff 命 令 的 结 果 如 下 : diff --git a/image.png b/image.png index 88839c4..4afcb7c a/image.png ,12 ExifTool Version Number : File Size : 70 kb -File Modification Date/Time : 2009:04:21 07:02:45-07:00 +File Size : 94 kb +File Modification Date/Time : 2009:04:21 07:02:43-07:00 File Type : PNG MIME Type : image/png -Image Width : Image Height : 889 +Image Width : Image Height : 827 Bit Depth : 8 Color Type : RGB with Alpha 你 一 眼 就 能 看 出 文 件 大 小 和 图 像 尺 寸 发 生 了 变 化 关 键 字 展 开 SVN 或 CVS 风 格 的 关 键 字 展 开 (keyword expansion) 功 能 经 常 会 被 习 惯 于 上 述 系 统 的 开 发 者 使 用 到 在 Git 中, 这 项 功 能 有 一 个 主 要 问 题, 就 是 你 无 法 利 用 它 往 文 件 中 加 入 其 关 联 提 交 的 相 关 信 息, 因 为 Git 总 是 先 对 文 件 做 校 验 和 运 算 ( 译 者 注 :Git 中 提 交 对 象 的 校 验 依 赖 于 文 件 的 校 验 和, 而 Git 属 性 针 对 特 定 文 件 或 路 径, 因 此 基 于 Git 属 性 的 关 键 字 展 开 无 法 仅 根 据 文 件 反 推 出 对 应 的 提 交 ) 不 过, 我 们 可 以 在 检 出 某 个 文 件 后 对 其 注 入 文 本, 并 在 再 次 提 交 前 删 除 这 些 文 本 Git 属 性 提 供 了 两 种 方 法 来 达 到 这 一 目 的 一 种 方 法 是, 你 可 以 把 文 件 所 对 应 数 据 对 象 的 SHA-1 校 验 和 自 动 注 入 到 文 件 中 的 $Id$ 字 段 如 果 在 一 个 或 多 个 文 件 上 设 置 了 该 属 性, 下 次 当 你 检 出 相 关 分 支 的 时 候,Git 会 用 相 应 数 据 对 象 的 SHA-1 值 替 换 上 述 字 段 注 意, 这 不 是 提 交 对 象 的 SHA-1 校 验 和, 而 是 数 据 对 象 本 身 的 校 验 和 : $ echo '*.txt ident' >>.gitattributes $ echo '$Id$' > test.txt

343 当 你 下 次 检 出 文 件 时,Git 将 注 入 数 据 对 象 的 SHA-1 校 验 和 : $ rm test.txt $ git checkout -- test.txt $ cat test.txt $Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $ 然 而, 这 个 结 果 的 用 途 比 较 有 限 如 果 用 过 CVS 或 Subversion 的 关 键 字 替 换 功 能, 我 们 会 想 加 上 一 个 时 间 戳 信 息 光 有 SHA-1 校 验 和 用 途 不 大, 它 仅 仅 是 个 随 机 字 符 串, 你 无 法 凭 字 面 值 来 区 分 不 同 SHA-1 时 间 上 的 先 后 因 此 Git 属 性 提 供 了 另 一 种 方 法 : 我 们 可 以 编 写 自 己 的 过 滤 器 来 实 现 文 件 提 交 或 检 出 时 的 关 键 字 替 换 一 个 过 滤 器 由 clean 和 smudge 两 个 子 过 滤 器 组 成 在.gitattributes 文 件 中, 你 能 对 特 定 的 路 径 设 置 一 个 过 滤 器, 然 后 设 置 文 件 检 出 前 的 处 理 脚 本 ( smudge, 见 smudge 过 滤 器 会 在 文 件 被 检 出 时 触 发 ) 和 文 件 暂 存 前 的 处 理 脚 本 ( clean, 见 clean 过 滤 器 会 在 文 件 被 暂 存 时 触 发 ) 这 两 个 过 滤 器 能 够 被 用 来 做 各 种 有 趣 的 事 Figure 144. smudge 过 滤 器 会 在 文 件 被 检 出 时 触 发

344 Figure 145. clean 过 滤 器 会 在 文 件 被 暂 存 时 触 发 在 (Git 源 码 中 ) 实 现 这 个 特 性 的 原 始 提 交 信 息 里 给 出 了 一 个 简 单 的 例 子 : 在 提 交 前, 用 indent 程 序 过 滤 所 有 C 源 码 你 可 以 在.gitattributes 文 件 中 对 filter 属 性 设 置 indent 过 滤 器 来 过 滤 *.c 文 件 *.c filter=indent 然 后, 通 过 以 下 配 置, 让 Git 知 道 indent 过 滤 器 在 smudge 和 clean 时 分 别 该 做 什 么 : $ git config --global filter.indent.clean indent $ git config --global filter.indent.smudge cat 在 这 个 例 子 中, 当 你 暂 存 *.c 文 件 时,indent 程 序 会 先 被 触 发 ; 在 把 它 们 检 出 回 硬 盘 时,cat 程 序 会 先 被 触 发 cat 在 这 里 没 什 么 实 际 作 用 : 它 仅 仅 把 输 入 的 数 据 重 新 输 出 这 样 的 组 合 可 以 有 效 地 在 暂 存 前 用 indent 过 滤 所 有 的 C 源 码 另 一 个 有 趣 的 例 子 是 实 现 RCS 风 格 的 $Date$ 关 键 字 展 开 要 想 演 示 这 个 例 子, 我 们 需 要 实 现 这 样 的 一 个 小 脚 本 : 接 受 文 件 名 参 数, 得 到 项 目 的 最 新 提 交 日 期, 并 把 日 期 写 入 该 文 件 下 面 是 一 个 实 现 了 该 功 能 的 Ruby 小 脚 本 : #! /usr/bin/env ruby data = STDIN.read last_date = `git log --pretty=format:"%ad" -1` puts data.gsub('$date$', '$Date: ' + last_date.to_s + '$') 这 个 脚 本 从 git log 中 得 到 最 新 提 交 日 期, 将 其 注 入 所 有 输 入 文 件 的 $Date$ 字 段, 并 输 出 结 果 你 可 以 使

345 用 最 顺 手 的 语 言 轻 松 实 现 一 个 类 似 的 脚 本 把 该 脚 本 命 名 为 expand_date, 放 到 你 的 可 执 行 路 径 中 现 在, 你 需 要 在 Git 中 设 置 一 个 过 滤 器 ( 就 叫 它 dater 吧 ), 让 它 在 检 出 文 件 时 调 用 你 的 expand_date 来 注 入 时 间 戳, 完 成 smudge 操 作 暂 存 文 件 时 的 clean 操 作 则 是 用 一 行 Perl 表 达 式 清 除 注 入 的 内 容 : $ git config filter.dater.smudge expand_date $ git config filter.dater.clean 'perl -pe "s/\\\$date[^\\\$]*\\\$/\\\$date\\\$/"' 这 段 Perl 代 码 会 删 除 $Date$ 后 面 注 入 的 内 容, 恢 复 它 的 原 貌 过 滤 器 终 于 准 备 完 成 了, 是 时 候 测 试 一 下 创 建 一 个 带 有 $Date$ 关 键 字 的 文 件, 然 后 给 它 设 置 一 个 Git 属 性, 关 联 我 们 的 新 过 滤 器 : $ echo '# $Date$' > date_test.txt $ echo 'date*.txt filter=dater' >>.gitattributes 提 交 该 文 件, 并 再 次 检 出, 你 会 发 现 关 键 字 如 期 被 替 换 了 : $ git add date_test.txt.gitattributes $ git commit -m "Testing date expansion in Git" $ rm date_test.txt $ git checkout date_test.txt $ cat date_test.txt # $Date: Tue Apr 21 07:26: $ 自 定 义 过 滤 器 真 的 很 强 大 不 过 你 需 要 注 意 的 是, 因 为.gitattributes 文 件 会 随 着 项 目 一 起 提 交, 而 过 滤 器 ( 例 如 这 里 的 dater) 不 会, 所 以 过 滤 器 有 可 能 会 失 效 当 你 在 设 计 这 些 过 滤 器 时, 要 注 重 容 错 性 它 们 在 出 错 时 应 该 能 优 雅 地 退 出, 从 而 不 至 于 影 响 项 目 的 正 常 运 行 导 出 版 本 库 Git 属 性 在 导 出 项 目 归 档 (archive) 时 也 能 发 挥 作 用 export-ignore 当 归 档 的 时 候, 可 以 设 置 Git 不 导 出 某 些 文 件 和 目 录 如 果 你 不 想 在 归 档 中 包 含 某 个 子 目 录 或 文 件, 但 想 把 它 们 纳 入 项 目 的 版 本 管 理 中, 你 可 以 在 export-ignore 属 性 中 指 定 它 们 例 如, 假 设 你 在 test/ 子 目 录 下 有 一 些 测 试 文 件, 不 希 望 它 们 被 包 含 在 项 目 导 出 的 压 缩 包 (tarball) 中 你 可 以 增 加 下 面 这 行 到 Git 属 性 文 件 中 : test/ export-ignore

346 现 在, 当 你 运 行 git archive 来 创 建 项 目 的 压 缩 包 时, 那 个 目 录 不 会 被 包 括 在 归 档 中 export-subst 在 导 出 文 件 进 行 部 署 的 时 候, 你 可 以 将 git log 的 格 式 化 和 关 键 字 展 开 处 理 应 用 于 被 export-subst 属 性 标 记 的 部 分 文 件 举 个 例 子, 如 果 你 想 在 项 目 中 包 含 一 个 叫 做 LAST_COMMIT 的 文 件, 并 在 运 行 git archive 的 时 候 自 动 向 它 注 入 最 新 提 交 的 元 数 据, 可 以 像 这 样 设 置 该 文 件 : $ echo 'Last commit date: $Format:%cd by %an$' > LAST_COMMIT $ echo "LAST_COMMIT export-subst" >>.gitattributes $ git add LAST_COMMIT.gitattributes $ git commit -am 'adding LAST_COMMIT file for archives' 运 行 git archive 之 后, 该 文 件 被 归 档 后 的 内 容 会 被 替 换 成 这 样 : $ git archive HEAD tar xcf../deployment-testing - $ cat../deployment-testing/last_commit Last commit date: Tue Apr 21 08:38: by Scott Chacon 你 也 可 以 用 诸 如 提 交 信 息 或 者 任 意 的 git 注 解 进 行 替 换, 并 且 git log 还 能 做 简 单 的 字 词 包 装 : $ echo '$Format:Last commit: %h by %an at %cd%n%+w(76,6,9)%b$' > LAST_COMMIT $ git commit -am 'export-subst 使 用 git log 的 自 定 义 格 式 化 工 具 git archive 直 接 使 用 git log 的 `pretty=format:` 处 理 器, 并 在 输 出 中 移 除 两 侧 的 `$Format:` 和 `$` 标 记 ' $ git tar xfo - LAST_COMMIT Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14: export-subst 使 用 git log 的 自 定 义 格 式 化 工 具 git archive 直 接 使 用 git log 的 `pretty=format:` 处 理 器, 并 在 输 出 中 移 除 两 侧 的 `$Format:` 和 `$` 标 记 由 此 得 到 的 归 档 适 用 于 ( 当 前 的 ) 部 署 工 作 然 而 和 其 他 的 导 出 归 档 一 样, 它 并 不 适 用 于 后 继 的 部 署 工 作 合 并 策 略 通 过 Git 属 性, 你 还 能 对 项 目 中 的 特 定 文 件 指 定 不 同 的 合 并 策 略 一 个 非 常 有 用 的 选 项 就 是, 告 诉 Git 当 特 定 文

347 件 发 生 冲 突 时 不 要 尝 试 合 并 它 们, 而 是 直 接 使 用 你 这 边 的 内 容 考 虑 如 下 场 景 : 项 目 中 有 一 个 分 叉 的 或 者 定 制 过 的 特 性 分 支, 你 希 望 该 分 支 上 的 更 改 能 合 并 回 你 的 主 干 分 支, 同 时 需 要 忽 略 其 中 某 些 文 件 此 时 这 个 合 并 策 略 就 能 派 上 用 场 假 设 你 有 一 个 数 据 库 设 置 文 件 database.xml, 在 两 个 分 支 中 它 是 不 同 的, 而 你 想 合 并 另 一 个 分 支 到 你 的 分 支 上, 又 不 想 弄 乱 该 数 据 库 文 件 你 可 以 设 置 属 性 如 下 : database.xml merge=ours 然 后 定 义 一 个 虚 拟 的 合 并 策 略, 叫 做 ours: $ git config --global merge.ours.driver true 如 果 你 合 并 了 另 一 个 分 支,database.xml 文 件 不 会 有 合 并 冲 突, 相 反 会 显 示 如 下 信 息 : $ git merge topic Auto-merging database.xml Merge made by recursive. 这 里,database.xml 保 持 了 主 干 分 支 中 的 原 始 版 本 Git 钩 子 和 其 它 版 本 控 制 系 统 一 样,Git 能 在 特 定 的 重 要 动 作 发 生 时 触 发 自 定 义 脚 本 有 两 组 这 样 的 钩 子 : 客 户 端 的 和 服 务 器 端 的 客 户 端 钩 子 由 诸 如 提 交 和 合 并 这 样 的 操 作 所 调 用, 而 服 务 器 端 钩 子 作 用 于 诸 如 接 收 被 推 送 的 提 交 这 样 的 联 网 操 作 你 可 以 随 心 所 欲 地 运 用 这 些 钩 子 安 装 一 个 钩 子 钩 子 都 被 存 储 在 Git 目 录 下 的 hooks 子 目 录 中 也 即 绝 大 部 分 项 目 中 的.git/hooks 当 你 用 git init 初 始 化 一 个 新 版 本 库 时,Git 默 认 会 在 这 个 目 录 中 放 置 一 些 示 例 脚 本 这 些 脚 本 除 了 本 身 可 以 被 调 用 外, 它 们 还 透 露 了 被 触 发 时 所 传 入 的 参 数 所 有 的 示 例 都 是 shell 脚 本, 其 中 一 些 还 混 杂 了 Perl 代 码, 不 过, 任 何 正 确 命 名 的 可 执 行 脚 本 都 可 以 正 常 使 用 你 可 以 用 Ruby 或 Python, 或 其 它 语 言 编 写 它 们 这 些 示 例 的 名 字 都 是 以.sample 结 尾, 如 果 你 想 启 用 它 们, 得 先 移 除 这 个 后 缀 把 一 个 正 确 命 名 且 可 执 行 的 文 件 放 入 Git 目 录 下 的 hooks 子 目 录 中, 即 可 激 活 该 钩 子 脚 本 这 样 一 来, 它 就 能 被 Git 调 用 接 下 来, 我 们 会 讲 解 常 用 的 钩 子 脚 本 类 型 客 户 端 钩 子 客 户 端 钩 子 分 为 很 多 种 下 面 把 它 们 分 为 : 提 交 工 作 流 钩 子 电 子 邮 件 工 作 流 钩 子 和 其 它 钩 子

348 NOTE 需 要 注 意 的 是, 克 隆 某 个 版 本 库 时, 它 的 客 户 端 钩 子 并 不 随 同 复 制 如 果 需 要 靠 这 些 脚 本 来 强 制 维 持 某 种 策 略, 建 议 你 在 服 务 器 端 实 现 这 一 功 能 ( 请 参 照 使 用 强 制 策 略 的 一 个 例 子 中 的 例 子 ) 提 交 工 作 流 钩 子 前 四 个 钩 子 涉 及 提 交 的 过 程 pre-commit 钩 子 在 键 入 提 交 信 息 前 运 行 它 用 于 检 查 即 将 提 交 的 快 照, 例 如, 检 查 是 否 有 所 遗 漏, 确 保 测 试 运 行, 以 及 核 查 代 码 如 果 该 钩 子 以 非 零 值 退 出,Git 将 放 弃 此 次 提 交, 不 过 你 可 以 用 git commit --no -verify 来 绕 过 这 个 环 节 你 可 以 利 用 该 钩 子, 来 检 查 代 码 风 格 是 否 一 致 ( 运 行 类 似 lint 的 程 序 ) 尾 随 空 白 字 符 是 否 存 在 ( 自 带 的 钩 子 就 是 这 么 做 的 ), 或 新 方 法 的 文 档 是 否 适 当 prepare-commit-msg 钩 子 在 启 动 提 交 信 息 编 辑 器 之 前, 默 认 信 息 被 创 建 之 后 运 行 它 允 许 你 编 辑 提 交 者 所 看 到 的 默 认 信 息 该 钩 子 接 收 一 些 选 项 : 存 有 当 前 提 交 信 息 的 文 件 的 路 径 提 交 类 型 和 修 补 提 交 的 提 交 的 SHA-1 校 验 它 对 一 般 的 提 交 来 说 并 没 有 什 么 用 ; 然 而 对 那 些 会 自 动 产 生 默 认 信 息 的 提 交, 如 提 交 信 息 模 板 合 并 提 交 压 缩 提 交 和 修 订 提 交 等 非 常 实 用 你 可 以 结 合 提 交 模 板 来 使 用 它, 动 态 地 插 入 信 息 commit-msg 钩 子 接 收 一 个 参 数, 此 参 数 即 上 文 提 到 的, 存 有 当 前 提 交 信 息 的 临 时 文 件 的 路 径 如 果 该 钩 子 脚 本 以 非 零 值 退 出,Git 将 放 弃 提 交, 因 此, 可 以 用 来 在 提 交 通 过 前 验 证 项 目 状 态 或 提 交 信 息 在 本 章 的 最 后 一 节, 我 们 将 展 示 如 何 使 用 该 钩 子 来 核 对 提 交 信 息 是 否 遵 循 指 定 的 模 板 post-commit 钩 子 在 整 个 提 交 过 程 完 成 后 运 行 它 不 接 收 任 何 参 数, 但 你 可 以 很 容 易 地 通 过 运 行 git log -1 HEAD 来 获 得 最 后 一 次 的 提 交 信 息 该 钩 子 一 般 用 于 通 知 之 类 的 事 情 电 子 邮 件 工 作 流 钩 子 你 可 以 给 电 子 邮 件 工 作 流 设 置 三 个 客 户 端 钩 子 它 们 都 是 由 git am 命 令 调 用 的, 因 此 如 果 你 没 有 在 你 的 工 作 流 中 用 到 这 个 命 令, 可 以 跳 到 下 一 节 如 果 你 需 要 通 过 电 子 邮 件 接 收 由 git format-patch 产 生 的 补 丁, 这 些 钩 子 也 许 用 得 上 第 一 个 运 行 的 钩 子 是 applypatch-msg 它 接 收 单 个 参 数 : 包 含 请 求 合 并 信 息 的 临 时 文 件 的 名 字 如 果 脚 本 返 回 非 零 值,Git 将 放 弃 该 补 丁 你 可 以 用 该 脚 本 来 确 保 提 交 信 息 符 合 格 式, 或 直 接 用 脚 本 修 正 格 式 错 误 下 一 个 在 git am 运 行 期 间 被 调 用 的 是 pre-applypatch 有 些 难 以 理 解 的 是, 它 正 好 运 行 于 应 用 补 丁 之 后, 产 生 提 交 之 前, 所 以 你 可 以 用 它 在 提 交 前 检 查 快 照 你 可 以 用 这 个 脚 本 运 行 测 试 或 检 查 工 作 区 如 果 有 什 么 遗 漏, 或 测 试 未 能 通 过, 脚 本 会 以 非 零 值 退 出, 中 断 git am 的 运 行, 这 样 补 丁 就 不 会 被 提 交 post-applypatch 运 行 于 提 交 产 生 之 后, 是 在 git am 运 行 期 间 最 后 被 调 用 的 钩 子 你 可 以 用 它 把 结 果 通 知 给 一 个 小 组 或 所 拉 取 的 补 丁 的 作 者 但 你 没 办 法 用 它 停 止 打 补 丁 的 过 程 其 它 客 户 端 钩 子 pre-rebase 钩 子 运 行 于 变 基 之 前, 以 非 零 值 退 出 可 以 中 止 变 基 的 过 程 你 可 以 使 用 这 个 钩 子 来 禁 止 对 已 经 推 送 的 提 交 变 基 Git 自 带 的 pre-rebase 钩 子 示 例 就 是 这 么 做 的, 不 过 它 所 做 的 一 些 假 设 可 能 与 你 的 工 作 流 程 不 匹 配

349 post-rewrite 钩 子 被 那 些 会 替 换 提 交 记 录 的 命 令 调 用, 比 如 git commit --amend 和 git rebase( 不 过 不 包 括 git filter-branch) 它 唯 一 的 参 数 是 触 发 重 写 的 命 令 名, 同 时 从 标 准 输 入 中 接 受 一 系 列 重 写 的 提 交 记 录 这 个 钩 子 的 用 途 很 大 程 度 上 跟 post-checkout 和 post-merge 差 不 多 在 git checkout 成 功 运 行 后,post-checkout 钩 子 会 被 调 用 你 可 以 根 据 你 的 项 目 环 境 用 它 调 整 你 的 工 作 目 录 其 中 包 括 放 入 大 的 二 进 制 文 件 自 动 生 成 文 档 或 进 行 其 他 类 似 这 样 的 操 作 在 git merge 成 功 运 行 后,post-merge 钩 子 会 被 调 用 你 可 以 用 它 恢 复 Git 无 法 跟 踪 的 工 作 区 数 据, 比 如 权 限 数 据 这 个 钩 子 也 可 以 用 来 验 证 某 些 在 Git 控 制 之 外 的 文 件 是 否 存 在, 这 样 你 就 能 在 工 作 区 改 变 时, 把 这 些 文 件 复 制 进 来 pre-push 钩 子 会 在 git push 运 行 期 间, 更 新 了 远 程 引 用 但 尚 未 传 送 对 象 时 被 调 用 它 接 受 远 程 分 支 的 名 字 和 位 置 作 为 参 数, 同 时 从 标 准 输 入 中 读 取 一 系 列 待 更 新 的 引 用 你 可 以 在 推 送 开 始 之 前, 用 它 验 证 对 引 用 的 更 新 操 作 ( 一 个 非 零 的 退 出 码 将 终 止 推 送 过 程 ) Git 的 一 些 日 常 操 作 在 运 行 时, 偶 尔 会 调 用 git gc --auto 进 行 垃 圾 回 收 pre-auto-gc 钩 子 会 在 垃 圾 回 收 开 始 之 前 被 调 用, 可 以 用 它 来 提 醒 你 现 在 要 回 收 垃 圾 了, 或 者 依 情 形 判 断 是 否 要 中 断 回 收 服 务 器 端 钩 子 除 了 客 户 端 钩 子, 作 为 系 统 管 理 员, 你 还 可 以 使 用 若 干 服 务 器 端 的 钩 子 对 项 目 强 制 执 行 各 种 类 型 的 策 略 这 些 钩 子 脚 本 在 推 送 到 服 务 器 之 前 和 之 后 运 行 推 送 到 服 务 器 前 运 行 的 钩 子 可 以 在 任 何 时 候 以 非 零 值 退 出, 拒 绝 推 送 并 给 客 户 端 返 回 错 误 消 息, 还 可 以 依 你 所 想 设 置 足 够 复 杂 的 推 送 策 略 pre-receive 处 理 来 自 客 户 端 的 推 送 操 作 时, 最 先 被 调 用 的 脚 本 是 pre-receive 它 从 标 准 输 入 获 取 一 系 列 被 推 送 的 引 用 如 果 它 以 非 零 值 退 出, 所 有 的 推 送 内 容 都 不 会 被 接 受 你 可 以 用 这 个 钩 子 阻 止 对 引 用 进 行 非 快 进 (non-fastforward) 的 更 新, 或 者 对 该 推 送 所 修 改 的 所 有 引 用 和 文 件 进 行 访 问 控 制 update update 脚 本 和 pre-receive 脚 本 十 分 类 似, 不 同 之 处 在 于 它 会 为 每 一 个 准 备 更 新 的 分 支 各 运 行 一 次 假 如 推 送 者 同 时 向 多 个 分 支 推 送 内 容,pre-receive 只 运 行 一 次, 相 比 之 下 update 则 会 为 每 一 个 被 推 送 的 分 支 各 运 行 一 次 它 不 会 从 标 准 输 入 读 取 内 容, 而 是 接 受 三 个 参 数 : 引 用 的 名 字 ( 分 支 ), 推 送 前 的 引 用 指 向 的 内 容 的 SHA-1 值, 以 及 用 户 准 备 推 送 的 内 容 的 SHA-1 值 如 果 update 脚 本 以 非 零 值 退 出, 只 有 相 应 的 那 一 个 引 用 会 被 拒 绝 ; 其 余 的 依 然 会 被 更 新 post-receive post-receive 挂 钩 在 整 个 过 程 完 结 以 后 运 行, 可 以 用 来 更 新 其 他 系 统 服 务 或 者 通 知 用 户 它 接 受 与 prereceive 相 同 的 标 准 输 入 数 据 它 的 用 途 包 括 给 某 个 邮 件 列 表 发 信, 通 知 持 续 集 成 (continous integration) 的 服 务 器, 或 者 更 新 问 题 追 踪 系 统 (ticket-tracking system) 甚 至 可 以 通 过 分 析 提 交 信 息 来 决 定 某 个 问 题 (ticket) 是 否 应 该 被 开 启, 修 改 或 者 关 闭 该 脚 本 无 法 终 止 推 送 进 程, 不 过 客 户 端 在 它 结 束 运 行 之 前 将 保 持 连 接 状 态, 所 以 如 果 你 想 做 其 他 操 作 需 谨 慎 使 用 它, 因 为 它 将 耗 费 你 很 长 的 一 段 时 间

350 使 用 强 制 策 略 的 一 个 例 子 在 本 节 中, 你 将 应 用 前 面 学 到 的 知 识 建 立 这 样 一 个 Git 工 作 流 程 : 检 查 提 交 信 息 的 格 式, 并 且 指 定 只 能 由 特 定 用 户 修 改 项 目 中 特 定 的 子 目 录 你 将 编 写 一 个 客 户 端 脚 本 来 提 示 开 发 人 员 他 们 的 推 送 是 否 会 被 拒 绝, 以 及 一 个 服 务 器 端 脚 本 来 实 际 执 行 这 些 策 略 我 们 待 会 展 示 的 脚 本 是 用 Ruby 写 的, 部 分 是 由 于 我 习 惯 用 它 写 脚 本, 另 外 也 因 为 Ruby 简 单 易 懂, 即 便 你 没 写 过 它 也 能 看 明 白 不 过 任 何 其 他 语 言 也 一 样 适 用 所 有 Git 自 带 的 示 例 钩 子 脚 本 都 是 用 Perl 或 Bash 写 的, 所 以 你 能 从 它 们 中 找 到 相 当 多 的 这 两 种 语 言 的 钩 子 示 例 服 务 器 端 钩 子 所 有 服 务 器 端 的 工 作 都 将 在 你 的 hooks 目 录 下 的 update 脚 本 中 完 成 update 脚 本 会 为 每 一 个 提 交 的 分 支 各 运 行 一 次, 它 接 受 三 个 参 数 : 被 推 送 的 引 用 的 名 字 推 送 前 分 支 的 修 订 版 本 (revision) 用 户 准 备 推 送 的 修 订 版 本 (revision) 如 果 推 送 是 通 过 SSH 进 行 的, 还 可 以 获 知 进 行 此 次 推 送 的 用 户 的 信 息 如 果 你 允 许 所 有 操 作 都 通 过 公 匙 授 权 的 单 一 帐 号 ( 比 如 git ) 进 行, 就 有 必 要 通 过 一 个 shell 包 装 脚 本 依 据 公 匙 来 判 断 用 户 的 身 份, 并 且 相 应 地 设 定 环 境 变 量 来 表 示 该 用 户 的 身 份 下 面 就 假 设 $USER 环 境 变 量 里 存 储 了 当 前 连 接 的 用 户 的 身 份, 你 的 update 脚 本 首 先 搜 集 一 切 需 要 的 信 息 : #!/usr/bin/env ruby $refname = ARGV[0] $oldrev = ARGV[1] $newrev = ARGV[2] $user = ENV['USER'] puts "Enforcing Policies..." puts "(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})" 是 的, 我 们 这 里 用 的 都 是 全 局 变 量 请 勿 在 此 吐 槽 这 样 做 只 是 为 了 方 便 展 示 而 已 指 定 特 殊 的 提 交 信 息 格 式 你 的 第 一 项 任 务 是 要 求 每 一 条 提 交 信 息 都 必 须 遵 循 某 种 特 殊 的 格 式 作 为 目 标, 假 定 每 一 条 信 息 必 须 包 含 一 条 形 似 ref: 1234 的 字 符 串, 因 为 你 想 把 每 一 次 提 交 对 应 到 问 题 追 踪 系 统 (ticketing system) 中 的 某 个 事 项 你 要 逐 一 检 查 每 一 条 推 送 上 来 的 提 交 内 容, 看 看 提 交 信 息 是 否 包 含 这 么 一 个 字 符 串, 然 后, 如 果 某 个 提 交 里 不 包 含 这 个 字 符 串, 以 非 零 返 回 值 退 出 从 而 拒 绝 此 次 推 送 把 $newrev 和 $oldrev 变 量 的 值 传 给 一 个 叫 做 git rev-list 的 Git 底 层 命 令, 你 可 以 获 取 所 有 提 交 的

351 SHA-1 值 列 表 git rev-list 基 本 类 似 git log 命 令, 但 它 默 认 只 输 出 SHA-1 值 而 已, 没 有 其 他 信 息 所 以 要 获 取 由 一 次 提 交 到 另 一 次 提 交 之 间 的 所 有 SHA-1 值, 可 以 像 这 样 运 行 : $ git rev-list 538c33..d14fc7 d14fc7c847ab946ec39590d87783c69b031bdfb7 9f585da4401b0a3999e d15245c13f0be a1be950e2a8d078e6141f5cd20c1e61ad3 dfa04c9ef3d f13fb5b9b1fb7717d2222a 17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475 你 可 以 截 取 这 些 输 出 内 容, 循 环 遍 历 其 中 每 一 个 SHA-1 值, 找 出 与 之 对 应 的 提 交 信 息, 然 后 用 正 则 表 达 式 来 测 试 该 信 息 包 含 的 内 容 下 一 步 要 实 现 从 每 个 提 交 中 提 取 出 提 交 信 息 使 用 另 一 个 叫 做 git cat-file 的 底 层 命 令 来 获 得 原 始 的 提 交 数 据 我 们 将 在 Git 内 部 原 理 了 解 到 这 些 底 层 命 令 的 细 节 ; 现 在 暂 时 先 看 一 下 这 条 命 令 的 输 出 : $ git cat-file commit ca82a6 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon <schacon@gmail.com> committer Scott Chacon <schacon@gmail.com> changed the version number 通 过 SHA-1 值 获 得 提 交 中 的 提 交 信 息 的 一 个 简 单 办 法 是 找 到 提 交 的 第 一 个 空 行, 然 后 取 从 它 往 后 的 所 有 内 容 可 以 使 用 Unix 系 统 的 sed 命 令 来 实 现 该 效 果 : $ git cat-file commit ca82a6 sed '1,/^$/d' changed the version number 你 可 以 用 这 条 咒 语 从 每 一 个 待 推 送 的 提 交 里 提 取 提 交 信 息, 然 后 在 提 取 的 内 容 不 符 合 要 求 时 退 出 为 了 退 出 脚 本 和 拒 绝 此 次 推 送, 返 回 非 零 值 整 个 脚 本 大 致 如 下 :

352 $regex = /\[ref: (\d+)\]/ # 指 定 自 定 义 的 提 交 信 息 格 式 def check_message_format missed_revs = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") missed_revs.each do rev message = `git cat-file commit #{rev} sed '1,/^$/d'` if!$regex.match(message) puts "[POLICY] Your message is not formatted correctly" exit 1 end end end check_message_format 把 这 一 段 放 在 update 脚 本 里, 所 有 包 含 不 符 合 指 定 规 则 的 提 交 都 会 遭 到 拒 绝 指 定 基 于 用 户 的 访 问 权 限 控 制 列 表 (ACL) 系 统 假 设 你 需 要 添 加 一 个 使 用 访 问 权 限 控 制 列 表 的 机 制, 来 指 定 哪 些 用 户 对 项 目 的 哪 些 部 分 有 推 送 权 限 某 些 用 户 具 有 全 部 的 访 问 权, 其 他 人 只 对 某 些 子 目 录 或 者 特 定 的 文 件 具 有 推 送 权 限 为 了 实 现 这 一 点, 你 要 把 相 关 的 规 则 写 入 位 于 服 务 器 原 始 Git 仓 库 的 acl 文 件 中 你 还 需 要 让 update 钩 子 检 阅 这 些 规 则, 审 视 推 送 的 提 交 内 容 中 被 修 改 的 所 有 文 件, 然 后 决 定 执 行 推 送 的 用 户 是 否 对 所 有 这 些 文 件 都 有 权 限 先 从 写 一 个 ACL 文 件 开 始 吧 这 里 使 用 的 格 式 和 CVS 的 ACL 机 制 十 分 类 似 : 它 由 若 干 行 构 成, 第 一 项 内 容 是 avail 或 者 unavail, 接 着 是 逗 号 分 隔 的 适 用 该 规 则 的 用 户 列 表, 最 后 一 项 是 适 用 该 规 则 的 路 径 ( 该 项 空 缺 表 示 没 有 路 径 限 制 ) 各 项 由 管 道 符 隔 开 在 本 例 中, 你 会 有 几 个 管 理 员, 一 些 对 doc 目 录 具 有 权 限 的 文 档 作 者, 以 及 一 位 仅 对 lib 和 tests 目 录 具 有 权 限 的 开 发 人 员, 相 应 的 ACL 文 件 如 下 : avail nickh,pjhyett,defunkt,tpw avail usinclair,cdickens,ebronte doc avail schacon lib avail schacon tests 首 先 把 这 些 数 据 读 入 你 要 用 到 的 数 据 结 构 里 在 本 例 中, 为 保 持 简 洁, 我 们 暂 时 只 实 现 avail 的 规 则 下 面 这 个 方 法 生 成 一 个 关 联 数 组, 它 的 键 是 用 户 名, 值 是 一 个 由 该 用 户 有 写 权 限 的 所 有 目 录 组 成 的 数 组 :

353 def get_acl_access_data(acl_file) # 读 取 ACL 数 据 acl_file = File.read(acl_file).split("\n").reject { line line == '' } access = {} acl_file.each do line avail, users, path = line.split(' ') next unless avail == 'avail' users.split(',').each do user access[user] = [] access[user] << path end end access end 对 于 之 前 给 出 的 ACL 规 则 文 件, 这 个 get_acl_access_data 方 法 返 回 的 数 据 结 构 如 下 : {"defunkt"=>[nil], "tpw"=>[nil], "nickh"=>[nil], "pjhyett"=>[nil], "schacon"=>["lib", "tests"], "cdickens"=>["doc"], "usinclair"=>["doc"], "ebronte"=>["doc"]} 既 然 拿 到 了 用 户 权 限 的 数 据, 接 下 来 你 需 要 找 出 提 交 都 修 改 了 哪 些 路 径, 从 而 才 能 保 证 推 送 者 对 所 有 这 些 路 径 都 有 权 限 使 用 git log 的 --name-only 选 项 ( 在 第 二 章 里 简 单 地 提 过 ), 我 们 可 以 轻 而 易 举 的 找 出 一 次 提 交 里 修 改 的 文 件 : $ git log -1 --name-only --pretty=format:'' 9f585d README lib/test.rb 使 用 get_acl_access_data 返 回 的 ACL 结 构 来 一 一 核 对 每 次 提 交 修 改 的 文 件 列 表, 就 能 找 出 该 用 户 是 否 有 权 限 推 送 所 有 的 提 交 内 容 :

354 # 仅 允 许 特 定 用 户 修 改 项 目 中 的 特 定 子 目 录 def check_directory_perms access = get_acl_access_data('acl') # 检 查 是 否 有 人 在 向 他 没 有 权 限 的 地 方 推 送 内 容 new_commits = `git rev-list #{$oldrev}..#{$newrev}`.split("\n") new_commits.each do rev files_modified = `git log -1 --name-only --pretty=format:'' #{rev}`.split("\n") files_modified.each do path next if path.size == 0 has_file_access = false access[$user].each do access_path if!access_path # 用 户 拥 有 完 全 访 问 权 限 (path.start_with? access_path) # 或 者 对 此 路 径 有 访 问 权 限 has_file_access = true end end if!has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end end check_directory_perms 通 过 git rev-list 获 取 推 送 到 服 务 器 的 所 有 提 交 接 着, 对 于 每 一 个 提 交, 找 出 它 修 改 的 文 件, 然 后 确 保 推 送 者 具 有 这 些 文 件 的 推 送 权 限 现 在 你 的 用 户 没 法 推 送 带 有 不 正 确 的 提 交 信 息 的 内 容, 也 不 能 在 准 许 他 们 访 问 范 围 之 外 的 位 置 做 出 修 改 测 试 一 下 如 果 已 经 把 上 面 的 代 码 放 到.git/hooks/update 文 件 里 了, 运 行 chmod u+x.git/hooks/update, 然 后 尝 试 推 送 一 个 不 符 合 格 式 的 提 交, 你 会 得 到 以 下 的 提 示 :

355 $ git push -f origin master Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 323 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. Enforcing Policies... (refs/heads/master) (8338c5) (c5b616) [POLICY] Your message is not formatted correctly error: hooks/update exited with error code 1 error: hook declined to update refs/heads/master To git@gitserver:project.git! [remote rejected] master -> master (hook declined) error: failed to push some refs to 'git@gitserver:project.git' 这 里 有 几 个 有 趣 的 信 息 首 先, 我 们 可 以 看 到 钩 子 运 行 的 起 点 Enforcing Policies... (refs/heads/master) (fb8c72) (c56860) 注 意 这 是 从 update 脚 本 开 头 输 出 到 标 准 输 出 的 所 有 从 脚 本 输 出 到 标 准 输 出 的 内 容 都 会 转 发 给 客 户 端 下 一 个 值 得 注 意 的 部 分 是 错 误 信 息 [POLICY] Your message is not formatted correctly error: hooks/update exited with error code 1 error: hook declined to update refs/heads/master 第 一 行 是 我 们 的 脚 本 输 出 的, 剩 下 两 行 是 Git 在 告 诉 我 们 update 脚 本 退 出 时 返 回 了 非 零 值 因 而 推 送 遭 到 了 拒 绝 最 后 一 点 : To git@gitserver:project.git! [remote rejected] master -> master (hook declined) error: failed to push some refs to 'git@gitserver:project.git' 你 会 看 到 每 个 被 你 的 钩 子 拒 之 门 外 的 引 用 都 收 到 了 一 个 remote rejected 信 息, 它 告 诉 你 正 是 钩 子 无 法 成 功 运 行 导 致 了 推 送 的 拒 绝 又 或 者 某 人 想 修 改 一 个 自 己 不 具 备 权 限 的 文 件 然 后 推 送 了 一 个 包 含 它 的 提 交, 他 将 看 到 类 似 的 提 示 比 如, 一 个 文 档 作 者 尝 试 推 送 一 个 修 改 到 lib 目 录 的 提 交, 他 会 看 到

356 [POLICY] You do not have access to push to lib/test.rb 从 今 以 后, 只 要 update 脚 本 存 在 并 且 可 执 行, 我 们 的 版 本 库 中 永 远 都 不 会 包 含 不 符 合 格 式 的 提 交 信 息, 并 且 用 户 都 会 待 在 沙 箱 里 面 客 户 端 钩 子 这 种 方 法 的 缺 点 在 于, 用 户 推 送 的 提 交 遭 到 拒 绝 后 无 法 避 免 的 抱 怨 辛 辛 苦 苦 写 成 的 代 码 在 最 后 时 刻 惨 遭 拒 绝 是 十 分 让 人 沮 丧 且 具 有 迷 惑 性 的 ; 更 可 怜 的 是 他 们 不 得 不 修 改 提 交 历 史 来 解 决 问 题, 这 个 方 法 并 不 能 让 每 一 个 人 满 意 逃 离 这 种 两 难 境 地 的 法 宝 是 给 用 户 一 些 客 户 端 的 钩 子, 在 他 们 犯 错 的 时 候 给 以 警 告 然 后 呢, 用 户 们 就 能 趁 问 题 尚 未 变 得 更 难 修 复, 在 提 交 前 消 除 这 个 隐 患 由 于 钩 子 本 身 不 跟 随 克 隆 的 项 目 副 本 分 发, 所 以 你 必 须 通 过 其 他 途 径 把 这 些 钩 子 分 发 到 用 户 的.git/hooks 目 录 并 设 为 可 执 行 文 件 虽 然 你 可 以 在 相 同 或 单 独 的 项 目 里 加 入 并 分 发 这 些 钩 子, 但 是 Git 不 会 自 动 替 你 设 置 它 首 先, 你 应 该 在 每 次 提 交 前 核 查 你 的 提 交 信 息, 这 样 才 能 确 保 服 务 器 不 会 因 为 不 合 条 件 的 提 交 信 息 而 拒 绝 你 的 更 改 为 了 达 到 这 个 目 的, 你 可 以 增 加 commit-msg 钩 子 如 果 你 使 用 该 钩 子 来 读 取 作 为 第 一 个 参 数 传 递 的 提 交 信 息, 然 后 与 规 定 的 格 式 作 比 较, 你 就 可 以 使 Git 在 提 交 信 息 格 式 不 对 的 情 况 下 拒 绝 提 交 #!/usr/bin/env ruby message_file = ARGV[0] message = File.read(message_file) $regex = /\[ref: (\d+)\]/ if!$regex.match(message) puts "[POLICY] Your message is not formatted correctly" exit 1 end 如 果 这 个 脚 本 位 于 正 确 的 位 置 (.git/hooks/commit-msg) 并 且 是 可 执 行 的, 你 提 交 信 息 的 格 式 又 是 不 正 确 的, 你 会 看 到 : $ git commit -am 'test' [POLICY] Your message is not formatted correctly 在 这 个 示 例 中, 提 交 没 有 成 功 然 而 如 果 你 的 提 交 注 释 信 息 是 符 合 要 求 的,Git 会 允 许 你 提 交 :

357 $ git commit -am 'test [ref: 132]' [master e05c914] test [ref: 132] 1 file changed, 1 insertions(+), 0 deletions(-) 接 下 来 我 们 要 保 证 没 有 修 改 到 ACL 允 许 范 围 之 外 的 文 件 假 如 你 的.git 目 录 下 有 前 面 使 用 过 的 那 份 ACL 文 件, 那 么 以 下 的 pre-commit 脚 本 将 把 里 面 的 规 定 执 行 起 来 : #!/usr/bin/env ruby $user = ENV['USER'] # [ 插 入 上 文 中 的 get_acl_access_data 方 法 ] # 仅 允 许 特 定 用 户 修 改 项 目 中 的 特 定 子 目 录 def check_directory_perms access = get_acl_access_data('.git/acl') files_modified = `git diff-index --cached --name-only HEAD`.split("\n") files_modified.each do path next if path.size == 0 has_file_access = false access[$user].each do access_path if!access_path (path.index(access_path) == 0) has_file_access = true end if!has_file_access puts "[POLICY] You do not have access to push to #{path}" exit 1 end end end check_directory_perms 这 和 服 务 器 端 的 脚 本 几 乎 一 样, 除 了 两 个 重 要 区 别 第 一,ACL 文 件 的 位 置 不 同, 因 为 这 个 脚 本 在 当 前 工 作 目 录 运 行, 而 非.git 目 录 ACL 文 件 的 路 径 必 须 从 access = get_acl_access_data('acl') 修 改 成 : access = get_acl_access_data('.git/acl')

358 另 一 个 重 要 区 别 是 获 取 被 修 改 文 件 列 表 的 方 式 在 服 务 器 端 的 时 候 使 用 了 查 看 提 交 纪 录 的 方 式, 可 是 目 前 的 提 交 都 还 没 被 记 录 下 来 呢, 所 以 这 个 列 表 只 能 从 暂 存 区 域 获 取 和 原 来 的 files_modified = `git log -1 --name-only --pretty=format:'' #{ref}` 不 同, 现 在 要 用 files_modified = `git diff-index --cached --name-only HEAD` 不 同 的 就 只 有 这 两 个 除 此 之 外, 该 脚 本 完 全 相 同 有 一 点 要 注 意 的 是, 它 假 定 在 本 地 运 行 的 用 户 和 推 送 到 远 程 服 务 器 端 的 相 同 如 果 这 二 者 不 一 样, 则 需 要 手 动 设 置 一 下 $user 变 量 在 这 里, 我 们 还 可 以 确 保 推 送 内 容 中 不 包 含 非 快 进 (non-fast-forward) 的 引 用 出 现 一 个 不 是 快 进 (fastforward) 的 引 用 有 两 种 情 形, 要 么 是 在 某 个 已 经 推 送 过 的 提 交 上 作 变 基, 要 么 是 从 本 地 推 送 一 个 错 误 的 分 支 到 远 程 分 支 上 假 定 为 了 执 行 这 个 策 略, 你 已 经 在 服 务 器 上 配 置 好 了 receive.denydeletes 和 receive.denynonfastforwards, 因 而 唯 一 还 需 要 避 免 的 是 在 某 个 已 经 推 送 过 的 提 交 上 作 变 基 下 面 是 一 个 检 查 这 个 问 题 的 pre-rebase 脚 本 示 例 它 获 取 所 有 待 重 写 的 提 交 的 列 表, 然 后 检 查 它 们 是 否 存 在 于 远 程 引 用 中 一 旦 发 现 其 中 一 个 提 交 是 在 某 个 远 程 引 用 中 可 达 的 (reachable), 它 就 终 止 此 次 变 基 :

359 #!/usr/bin/env ruby base_branch = ARGV[0] if ARGV[1] topic_branch = ARGV[1] else topic_branch = "HEAD" end target_shas = `git rev-list #{base_branch}..#{topic_branch}`.split("\n") remote_refs = `git branch -r`.split("\n").map { r r.strip } target_shas.each do sha remote_refs.each do remote_ref shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}` if shas_pushed.split("\n").include?(sha) puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}" exit 1 end end end 这 个 脚 本 利 用 了 一 个 第 六 章 修 订 版 本 选 择 一 节 中 不 曾 提 到 的 语 法 通 过 运 行 这 个 命 令 可 以 获 得 一 系 列 之 前 推 送 过 的 提 交 : `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}` SHA^@ 语 法 会 被 解 析 成 该 提 交 的 所 有 父 提 交 该 命 令 会 列 出 在 远 程 分 支 最 新 的 提 交 中 可 达 的, 却 在 所 有 我 们 尝 试 推 送 的 提 交 的 SHA-1 值 的 所 有 父 提 交 中 不 可 达 的 提 交 也 就 是 快 进 的 提 交 这 个 解 决 方 案 主 要 的 问 题 在 于 它 有 可 能 很 慢 而 且 常 常 没 有 必 要 只 要 你 不 用 -f 来 强 制 推 送, 服 务 器 就 会 自 动 给 出 警 告 并 且 拒 绝 接 受 推 送 然 而, 这 是 个 不 错 的 练 习, 而 且 理 论 上 能 帮 助 你 避 免 一 次 以 后 可 能 不 得 不 回 头 修 补 的 变 基 总 结 我 们 已 经 阐 述 了 大 部 分 通 过 自 定 义 Git 客 户 端 和 服 务 端 来 适 应 自 己 工 作 流 程 和 项 目 内 容 的 方 式 你 已 经 学 到 各 种 各 样 的 设 置 项 基 于 文 件 的 选 项 和 事 件 钩 子, 还 建 立 了 一 个 示 例 用 的 强 制 策 略 服 务 器 无 论 创 造 出 了 什 么 样 的 工 作 流 程, 你 都 能 使 Git 与 它 珠 联 璧 合

360 Git 与 其 他 系 统 现 实 并 不 总 是 尽 如 人 意 通 常, 你 不 能 立 刻 就 把 接 触 到 的 每 一 个 项 目 都 切 换 到 Git 有 时 候 你 被 困 在 使 用 其 他 VCS 的 项 目 中, 却 希 望 使 用 Git 在 本 章 的 第 一 部 分 我 们 将 会 了 解 到, 怎 样 在 你 的 那 些 托 管 在 不 同 系 统 的 项 目 上 使 用 Git 客 户 端 在 某 些 时 候, 你 可 能 想 要 将 已 有 项 目 转 换 到 Git 本 章 的 第 二 部 分 涵 盖 了 从 几 个 特 定 系 统 将 你 的 项 目 迁 移 至 Git 的 方 法, 即 使 没 有 预 先 构 建 好 的 导 入 工 具, 我 们 也 有 办 法 手 动 导 入 作 为 客 户 端 的 Git Git 为 开 发 者 提 供 了 如 此 优 秀 的 体 验, 许 多 人 已 经 找 到 了 在 他 们 的 工 作 站 上 使 用 Git 的 方 法, 即 使 他 们 团 队 其 余 的 人 使 用 的 是 完 全 不 同 的 VCS 有 许 多 这 种 可 用 的 适 配 器, 它 们 被 叫 做 桥 接 下 面 我 们 将 要 介 绍 几 个 很 可 能 会 在 实 际 中 用 到 的 桥 接 Git 与 Subversion 很 大 一 部 分 开 源 项 目 与 相 当 多 的 企 业 项 目 使 用 Subversion 来 管 理 它 们 的 源 代 码 而 且 在 大 多 数 时 间 里, 它 已 经 是 开 源 项 目 VCS 选 择 的 事 实 标 准 它 在 很 多 方 面 都 与 曾 经 是 源 代 码 管 理 世 界 的 大 人 物 的 CVS 相 似 Git 中 最 棒 的 特 性 就 是 有 一 个 与 Subversion 的 双 向 桥 接, 它 被 称 作 git svn 这 个 工 具 允 许 你 使 用 Git 作 为 连 接 到 Subversion 有 效 的 客 户 端, 这 样 你 可 以 使 用 Git 所 有 本 地 的 功 能 然 后 如 同 正 在 本 地 使 用 Subversion 一 样 推 送 到 Subversion 服 务 器 这 意 味 着 你 可 以 在 本 地 做 新 建 分 支 与 合 并 分 支 使 用 暂 存 区 使 用 变 基 与 拣 选 等 等 的 事 情, 同 时 协 作 者 还 在 继 续 使 用 他 们 黑 暗 又 古 老 的 方 式 当 你 试 图 游 说 公 司 将 基 础 设 施 修 改 为 完 全 支 持 Git 的 过 程 中, 一 个 好 方 法 是 将 Git 偷 偷 带 入 到 公 司 环 境, 并 帮 助 周 围 的 开 发 者 提 升 效 率 Subversion 桥 接 就 是 进 入 DVCS 世 界 的 诱 饵 git svn 在 Git 中 所 有 Subversion 桥 接 命 令 的 基 础 命 令 是 git svn 它 可 以 跟 很 多 命 令, 所 以 我 们 会 通 过 几 个 简 单 的 工 作 流 程 来 为 你 演 示 最 常 用 的 命 令 需 要 特 别 注 意 的 是 当 你 使 用 git svn 时, 就 是 在 与 Subversion 打 交 道, 一 个 与 Git 完 全 不 同 的 系 统 尽 管 可 以 在 本 地 新 建 分 支 与 合 并 分 支, 但 是 你 最 好 还 是 通 过 变 基 你 的 工 作 来 保 证 你 的 历 史 尽 可 能 是 直 线, 并 且 避 免 做 类 似 同 时 与 Git 远 程 服 务 器 交 互 的 事 情 不 要 重 写 你 的 历 史 然 后 尝 试 再 次 推 送, 同 时 也 不 要 推 送 到 一 个 平 行 的 Git 仓 库 来 与 其 他 使 用 Git 的 开 发 者 协 作 Subversion 只 能 有 一 个 线 性 的 历 史, 弄 乱 它 很 容 易 如 果 你 在 一 个 团 队 中 工 作, 其 中 有 一 些 人 使 用 SVN 而 另 一 些 人 使 用 Git, 你 需 要 确 保 每 个 人 都 使 用 SVN 服 务 器 来 协 作 - 这 样 做 会 省 去 很 多 麻 烦 设 置 为 了 演 示 这 个 功 能, 需 要 一 个 有 写 入 权 限 的 典 型 SVN 仓 库 如 果 想 要 拷 贝 这 些 例 子, 你 必 须 获 得 一 份 我 的 测 试 仓 库 的 可 写 拷 贝 为 了 轻 松 地 拷 贝, 可 以 使 用 Subversion 自 带 的 一 个 名 为 svnsync 的 工 具 为 了 这 些 测 试, 我 们 在 Google Code 上 创 建 了 一 个 protobuf 项 目 部 分 拷 贝 的 新 Subversion 仓 库 protobuf 是 一 个 将 结 构 性 数 据 编 码 用 于 网 络 传 输 的 工 具

361 接 下 来, 你 需 要 先 创 建 一 个 新 的 本 地 Subversion 仓 库 : $ mkdir /tmp/test-svn $ svnadmin create /tmp/test-svn 然 后, 允 许 所 有 用 户 改 变 版 本 属 性 - 最 容 易 的 方 式 是 添 加 一 个 返 回 值 为 0 的 pre-revprop-change 脚 本 $ cat /tmp/test-svn/hooks/pre-revprop-change #!/bin/sh exit 0; $ chmod +x /tmp/test-svn/hooks/pre-revprop-change 现 在 可 以 调 用 加 入 目 标 与 来 源 仓 库 参 数 的 svnsync init 命 令 同 步 这 个 项 目 到 本 地 的 机 器 $ svnsync init file:///tmp/test-svn \ 这 样 就 设 置 好 了 同 步 所 使 用 的 属 性 可 以 通 过 运 行 下 面 的 命 令 来 克 隆 代 码 : $ svnsync sync file:///tmp/test-svn Committed revision 1. Copied properties for revision 1. Transmitting file data...[...] Committed revision 2. Copied properties for revision 2. [ ] 虽 然 这 个 操 作 可 能 只 会 花 费 几 分 钟, 但 如 果 你 尝 试 拷 贝 原 始 的 仓 库 到 另 一 个 非 本 地 的 远 程 仓 库 时, 即 使 只 有 不 到 100 个 的 提 交, 这 个 过 程 也 可 能 会 花 费 将 近 一 个 小 时 Subversion 必 须 一 次 复 制 一 个 版 本 然 后 推 送 回 另 一 个 仓 库 - 这 低 效 得 可 笑, 但 却 是 做 这 件 事 唯 一 简 单 的 方 式 开 始 既 然 已 经 有 了 一 个 有 写 入 权 限 的 Subversion 仓 库, 那 么 你 可 以 开 始 一 个 典 型 的 工 作 流 程 可 以 从 git svn clone 命 令 开 始, 它 会 将 整 个 Subversion 仓 库 导 入 到 一 个 本 地 Git 仓 库 需 要 牢 记 的 一 点 是 如 果 是 从 一 个 真 正 托 管 的 Subversion 仓 库 中 导 入, 需 要 将 file:///tmp/test-svn 替 换 为 你 的 Subversion 仓 库 的 URL:

362 $ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags Initialized empty Git repository in /private/tmp/progit/test-svn/.git/ r1 = dcbfb cc2e8cc616cded (refs/remotes/origin/trunk) A m4/acx_pthread.m4 A m4/stl_hash.m4 A java/src/test/java/com/google/protobuf/unknownfieldsettest.java A java/src/test/java/com/google/protobuf/wireformattest.java r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk) Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75 Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae Following parent with do_switch Successfully followed parent r76 = 0fb585761df569eaecd8146c71e58d a2 (refs/remotes/origin/mycalc-branch) Checked out HEAD: file:///tmp/test-svn/trunk r75 这 相 当 于 运 行 了 两 个 命 令 - git svn init 以 及 紧 接 着 的 git svn fetch - 你 提 供 的 URL 这 会 花 费 一 些 时 间 测 试 项 目 只 有 75 个 左 右 的 提 交 并 且 代 码 库 并 不 是 很 大, 但 是 Git 必 须 一 次 一 个 地 检 出 一 个 版 本 同 时 单 独 地 提 交 它 对 于 有 成 百 上 千 个 提 交 的 项 目, 这 真 的 可 能 会 花 费 几 小 时 甚 至 几 天 来 完 成 -T trunk -b branches -t tags 部 分 告 诉 Git Subversion 仓 库 遵 循 基 本 的 分 支 与 标 签 惯 例 如 果 你 命 名 了 不 同 的 主 干 分 支 或 标 签, 可 以 修 改 这 些 参 数 因 为 这 是 如 此 地 常 见, 所 以 能 用 -s 来 替 代 整 个 这 部 分, 这 表 示 标 准 布 局 并 且 指 代 所 有 那 些 选 项 下 面 的 命 令 是 相 同 的 : $ git svn clone file:///tmp/test-svn -s 至 此, 应 该 得 到 了 一 个 已 经 导 入 了 分 支 与 标 签 的 有 效 的 Git 仓 库 : $ git branch -a * master remotes/origin/my-calc-branch remotes/origin/tags/2.0.2 remotes/origin/tags/release remotes/origin/tags/release remotes/origin/tags/release-2.0.2rc1 remotes/origin/trunk 注 意 这 个 工 具 是 如 何 将 Subversion 标 签 作 为 远 程 引 用 来 管 理 的 让 我 们 近 距 离 看 一 下 Git 的 底 层 命 令 showref:

363 $ git show-ref 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master 0fb585761df569eaecd8146c71e58d a2 refs/remotes/origin/my-calcbranch bfd2d fc73af a4b35c12f0b refs/remotes/origin/tags/ c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release rc1 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk Git 在 从 Git 服 务 器 克 隆 时 并 不 这 样 做 ; 下 面 是 在 刚 刚 克 隆 完 成 的 有 标 签 的 仓 库 的 样 子 : $ git show-ref c3dcbe8488c e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master 32ef1d1c7cc8c603ab cc421b80a8c2df refs/remotes/origin/branch-1 75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2 23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0 Git 直 接 将 标 签 抓 取 至 refs/tags, 而 不 是 将 它 们 看 作 分 支 提 交 回 Subversion 现 在 你 有 了 一 个 工 作 仓 库, 你 可 以 在 项 目 上 做 一 些 改 动, 然 后 高 效 地 使 用 Git 作 为 SVN 客 户 端 将 你 的 提 交 推 送 到 上 游 一 旦 编 辑 了 一 个 文 件 并 提 交 它, 你 就 有 了 一 个 存 在 于 本 地 Git 仓 库 的 提 交, 这 提 交 在 Subversion 服 务 器 上 并 不 存 在 : $ git commit -am 'Adding git-svn instructions to the README' [master 4af61fd] Adding git-svn instructions to the README 1 file changed, 5 insertions(+) 接 下 来, 你 需 要 将 改 动 推 送 到 上 游 注 意 这 会 怎 样 改 变 你 使 用 Subversion 的 方 式 - 你 可 以 离 线 做 几 次 提 交 然 后 一 次 性 将 它 们 推 送 到 Subversion 服 务 器 要 推 送 到 一 个 Subversion 服 务 器, 运 行 git svn dcommit 命 令 :

364 $ git svn dcommit Committing to file:///tmp/test-svn/trunk... M README.txt Committed r77 M README.txt r77 = 95e0222ba eb10afcd73e0670bc5 (refs/remotes/origin/trunk) No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk Resetting to the latest refs/remotes/origin/trunk 这 会 拿 走 你 在 Subversion 服 务 器 代 码 之 上 所 做 的 所 有 提 交, 针 对 每 一 个 做 一 个 Subversion 提 交, 然 后 重 写 你 本 地 的 Git 提 交 来 包 含 一 个 唯 一 的 标 识 符 这 很 重 要 因 为 这 意 味 着 所 有 你 的 提 交 的 SHA-1 校 验 和 都 改 变 了 部 分 由 于 这 个 原 因, 同 时 使 用 一 个 基 于 Git 的 项 目 远 程 版 本 和 一 个 Subversion 服 务 器 并 不 是 一 个 好 主 意 如 果 你 查 看 最 后 一 次 提 交, 有 新 的 git-svn-id 被 添 加 : $ git log -1 commit 95e0222ba eb10afcd73e0670bc5 Author: ben <ben@0b684db3-b d1-21af03df0a68> Date: Thu Jul 24 03:08: Adding git-svn instructions to the README git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b d1-21af03df0a68 注 意 你 原 来 提 交 的 SHA-1 校 验 和 原 来 是 以 4af61fd 开 头, 而 现 在 是 以 95e0222 开 头 如 果 想 要 既 推 送 到 一 个 Git 服 务 器 又 推 送 到 一 个 Subversion 服 务 器, 必 须 先 推 送 (dcommit) 到 Subversion 服 务 器, 因 为 这 个 操 作 会 改 变 你 的 提 交 数 据 拉 取 新 改 动 如 果 你 和 其 他 开 发 者 一 起 工 作, 当 在 某 一 时 刻 你 们 其 中 之 一 推 送 时, 另 一 人 尝 试 推 送 修 改 会 导 致 冲 突 那 次 修 改 会 被 拒 绝 直 到 你 合 并 他 们 的 工 作 在 git svn 中, 它 看 起 来 是 这 样 的 :

365 $ git svn dcommit Committing to file:///tmp/test-svn/trunk... ERROR from SVN: Transaction is out of date: File '/trunk/readme.txt' is out of date W: d5837c4b461b7c0e018b49d d2bfc240a and refs/remotes/origin/trunk differ, using rebase: : f414c433af0fd cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M README.txt Current branch master is up to date. ERROR: Not all changes have been committed into SVN, however the committed ones (if any) seem to be successfully integrated into the working tree. Please see the above messages for details. 为 了 解 决 这 种 情 况, 可 以 运 行 git svn rebase, 它 会 从 服 务 器 拉 取 任 何 你 本 地 还 没 有 的 改 动, 并 将 你 所 有 的 工 作 变 基 到 服 务 器 的 内 容 之 上 : $ git svn rebase Committing to file:///tmp/test-svn/trunk... ERROR from SVN: Transaction is out of date: File '/trunk/readme.txt' is out of date W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase: : c6e30d263495c17d781962cfff a b34372b25ccf4945fe5658fa381b075045e7702a M README.txt First, rewinding head to replay your work on top of it... Applying: update foo Using index info to reconstruct a base tree... M README.txt Falling back to patching base and 3-way merge... Auto-merging README.txt ERROR: Not all changes have been committed into SVN, however the committed ones (if any) seem to be successfully integrated into the working tree. Please see the above messages for details. 现 在, 所 有 你 的 工 作 都 已 经 在 Subversion 服 务 器 的 内 容 之 上 了, 你 就 可 以 顺 利 地 dcommit:

366 $ git svn dcommit Committing to file:///tmp/test-svn/trunk... M README.txt Committed r85 M README.txt r85 = 9c29704cc0bbbed7bd58160cfb66cb cd8 (refs/remotes/origin/trunk) No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk Resetting to the latest refs/remotes/origin/trunk 注 意, 和 Git 需 要 你 在 推 送 前 合 并 本 地 还 没 有 的 上 游 工 作 不 同 的 是,git svn 只 会 在 修 改 发 生 冲 突 时 要 求 你 那 样 做 ( 更 像 是 Subversion 工 作 的 行 为 ) 如 果 其 他 人 推 送 一 个 文 件 的 修 改 然 后 你 推 送 了 另 一 个 文 件 的 修 改, 你 的 dcommit 命 令 会 正 常 工 作 : $ git svn dcommit Committing to file:///tmp/test-svn/trunk... M configure.ac Committed r87 M autogen.sh r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk) M configure.ac r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk) W: a0253d aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase: : efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a d80d5d43bb65d4a7d0389ed6d M autogen.sh First, rewinding head to replay your work on top of it... 记 住 这 一 点 很 重 要, 因 为 结 果 是 当 你 推 送 后 项 目 的 状 态 并 不 存 在 于 你 的 电 脑 中 如 果 修 改 并 未 冲 突 但 却 是 不 兼 容 的, 可 能 会 引 起 一 些 难 以 诊 断 的 问 题 这 与 使 用 Git 服 务 器 并 不 同 - 在 Git 中, 可 以 在 发 布 前 完 全 测 试 客 户 端 系 统 的 状 态, 然 而 在 SVN 中, 你 甚 至 不 能 立 即 确 定 在 提 交 前 与 提 交 后 的 状 态 是 相 同 的 你 也 应 该 运 行 这 个 命 令 从 Subversion 服 务 器 上 拉 取 修 改, 即 使 你 自 己 并 不 准 备 提 交 可 以 运 行 git svn fetch 来 抓 取 新 数 据, 但 是 git svn rebase 会 抓 取 并 更 新 你 本 地 的 提 交 $ git svn rebase M autogen.sh r88 = c9c5f83c64bd b444bc7a0216cc1e17b (refs/remotes/origin/trunk) First, rewinding head to replay your work on top of it... Fast-forwarded master to refs/remotes/origin/trunk. 每 隔 一 会 儿 运 行 git svn rebase 确 保 你 的 代 码 始 终 是 最 新 的 虽 然 需 要 保 证 当 运 行 这 个 命 令 时 工 作 目 录 是 干 净 的 如 果 有 本 地 的 修 改, 在 运 行 git svn rebase 之 前 要 么 储 藏 你 的 工 作 要 么 做 一 次 临 时 的 提 交, 不 然, 当

367 变 基 会 导 致 合 并 冲 突 时, 命 令 会 终 止 Git 分 支 问 题 当 适 应 了 Git 的 工 作 流 程, 你 大 概 会 想 要 创 建 特 性 分 支, 在 上 面 做 一 些 工 作, 然 后 将 它 们 合 并 入 主 分 支 如 果 你 正 通 过 git svn 推 送 到 一 个 Subversion 服 务 器, 你 可 能 想 要 把 你 的 工 作 变 基 到 一 个 单 独 的 分 支 上, 而 不 是 将 分 支 合 并 到 一 起 比 较 喜 欢 变 基 的 原 因 是 因 为 Subversion 有 一 个 线 性 的 历 史 并 且 无 法 像 Git 一 样 处 理 合 并, 所 以 git svn 在 将 快 照 转 换 成 Subversion 提 交 时, 只 会 保 留 第 一 父 提 交 假 设 你 的 历 史 像 下 面 这 样 : 创 建 了 一 个 experiment 分 支, 做 了 两 次 提 交, 然 后 将 它 们 合 并 回 master 当 dcommit 时, 你 看 到 输 出 是 这 样 的 : $ git svn dcommit Committing to file:///tmp/test-svn/trunk... M CHANGES.txt Committed r89 M CHANGES.txt r89 = 89d492c884ea7c d5d913c6adf (refs/remotes/origin/trunk) M COPYING.txt M INSTALL.txt Committed r90 M INSTALL.txt M COPYING.txt r90 = cb e f6721bcf9a0 (refs/remotes/origin/trunk) No changes between 71af502c214ba f f55fd and refs/remotes/origin/trunk Resetting to the latest refs/remotes/origin/trunk 在 一 个 合 并 过 历 史 提 交 的 分 支 上 dcommit 命 令 工 作 得 很 好, 除 了 当 你 查 看 你 的 Git 项 目 历 史 时, 它 并 没 有 重 写 所 有 你 在 experiment 分 支 上 所 做 的 任 意 提 交 - 相 反, 所 有 这 些 修 改 显 示 一 个 单 独 合 并 提 交 的 SVN 版 本 中 当 其 他 人 克 隆 那 些 工 作 时, 他 们 只 会 看 到 一 个 被 塞 入 了 所 有 改 动 的 合 并 提 交, 就 像 运 行 了 git merge --squash; 他 们 无 法 看 到 修 改 从 哪 来 或 何 时 提 交 的 信 息 Subversion 分 支 在 Subversion 中 新 建 分 支 与 在 Git 中 新 建 分 支 并 不 相 同 ; 如 果 你 能 不 用 它, 那 最 好 就 不 要 用 然 而, 你 可 以 使 用 git svn 在 Subversion 中 创 建 分 支 并 在 分 支 上 做 提 交 创 建 一 个 新 的 SVN 分 支 要 在 Subversion 中 创 建 一 个 新 分 支, 运 行 git svn branch [branchname]:

368 $ git svn branch opera Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/testsvn/branches/opera... Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90 Found branch parent: (refs/remotes/origin/opera) cb e f6721bcf9a0 Following parent with do_switch Successfully followed parent r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f (refs/remotes/origin/opera) 这 与 Subversion 中 的 svn copy trunk branches/opera 命 令 作 用 相 同 并 且 是 在 Subversion 服 务 器 中 操 作 需 要 重 点 注 意 的 是 它 并 不 会 检 出 到 那 个 分 支 ; 如 果 你 在 这 时 提 交, 提 交 会 进 入 服 务 器 的 trunk 分 支, 而 不 是 opera 分 支 切 换 活 动 分 支 Git 通 过 查 找 在 历 史 中 Subversion 分 支 的 头 部 来 指 出 你 的 提 交 将 会 到 哪 一 个 分 支 - 应 该 只 有 一 个, 并 且 它 应 该 是 在 当 前 分 支 历 史 中 最 后 一 个 有 git-svn-id 的 如 果 想 要 同 时 在 不 止 一 个 分 支 上 工 作, 可 以 通 过 在 导 入 的 那 个 分 支 的 Subversion 提 交 开 始 来 设 置 本 地 分 支 dcommit 到 特 定 的 Subversion 分 支 如 果 想 要 一 个 可 以 单 独 在 上 面 工 作 的 opera 分 支, 可 以 运 行 $ git branch opera remotes/origin/opera 现 在, 如 果 想 要 将 你 的 opera 分 支 合 并 入 trunk( 你 的 master 分 支 ), 可 以 用 一 个 正 常 的 git merge 来 这 样 做 但 是 你 需 要 通 过 -m 来 提 供 一 个 描 述 性 的 提 交 信 息, 否 则 合 并 信 息 会 是 没 有 用 的 Merge branch opera 记 住 尽 管 使 用 的 是 git merge 来 做 这 个 操 作, 而 且 合 并 可 能 会 比 在 Subversion 中 更 容 易 一 些 ( 因 为 Git 会 为 你 自 动 地 检 测 合 适 的 合 并 基 础 ), 但 这 并 不 是 一 个 普 通 的 Git 合 并 提 交 你 不 得 不 将 这 个 数 据 推 送 回 一 个 Subversion 服 务 器,Subversion 服 务 器 不 支 持 那 些 跟 踪 多 个 父 结 点 的 提 交 ; 所 以, 当 推 送 完 成 后, 它 看 起 来 会 是 一 个 将 其 他 分 支 的 所 有 提 交 压 缩 在 一 起 的 单 独 提 交 在 合 并 一 个 分 支 到 另 一 个 分 支 后, 你 并 不 能 像 Git 中 那 样 轻 松 地 回 到 原 来 的 分 支 继 续 工 作 你 运 行 的 dcommit 命 令 会 将 哪 个 分 支 被 合 并 进 来 的 信 息 抹 掉, 所 以 后 续 的 合 并 基 础 计 算 会 是 错 的 - dcommit 会 使 你 的 git merge 结 果 看 起 来 像 是 运 行 了 git merge --squash 不 幸 的 是, 没 有 一 个 好 的 方 式 来 避 免 这 种 情 形 - Subversion 无 法 存 储 这 个 信 息, 所 以 当 使 用 它 做 为 服 务 器 时 你 总 是 会 被 它 的 限 制 打 垮 为 了 避 免 这 些 问 题, 应 该 在 合 并 到 主 干 后 删 除 本 地 分 支 ( 本 例 中 是 opera) Subversion 命 令 git svn 工 具 集 通 过 提 供 很 多 功 能 与 Subversion 中 那 些 相 似 的 命 令 来 帮 助 简 化 转 移 到 Git 的 过 程 下 面 是 一 些 提 供 了 Subversion 中 常 用 功 能 的 命 令

369 SVN 风 格 历 史 如 果 你 习 惯 于 使 用 Subversion 并 且 想 要 看 SVN 输 出 风 格 的 提 交 历 史, 可 以 运 行 git svn log 来 查 看 SVN 格 式 的 提 交 历 史 : $ git svn log r87 schacon :07: (Sat, 02 May 2014) 2 lines autogen change r86 schacon :00: (Sat, 02 May 2014) 2 lines Merge branch 'experiment' r85 schacon :00: (Sat, 02 May 2014) 2 lines updated the changelog 关 于 git svn log, 有 两 件 重 要 的 事 你 应 该 知 道 首 先, 它 是 离 线 工 作 的, 并 不 像 真 正 的 svn log 命 令, 会 向 Subversion 服 务 器 询 问 数 据 其 次, 它 只 会 显 示 已 经 提 交 到 Subversion 服 务 器 上 的 提 交 还 未 dcommit 的 本 地 Git 提 交 并 不 会 显 示 ; 同 样 也 不 会 显 示 这 段 时 间 中 其 他 人 推 送 到 Subversion 服 务 器 上 的 提 交 它 更 像 是 最 后 获 取 到 的 Subversion 服 务 器 上 的 提 交 状 态 SVN 注 解 类 似 git svn log 命 令 离 线 模 拟 了 svn log 命 令, 你 可 以 认 为 git svn blame [FILE] 离 线 模 拟 了 svn annotate 输 出 看 起 来 像 这 样 : $ git svn blame README.txt 2 temporal Protocol Buffers - Google's data interchange format 2 temporal Copyright 2008 Google Inc. 2 temporal 2 temporal 22 temporal C++ Installation - Unix 22 temporal ======================= 2 temporal 79 schacon Committing in git-svn. 78 schacon 2 temporal To build and install the C++ Protocol Buffer runtime and the Protocol 2 temporal Buffer compiler (protoc) execute the following: 2 temporal

370 重 复 一 次, 它 并 不 显 示 你 在 Git 中 的 本 地 提 交, 也 不 显 示 同 一 时 间 被 推 送 到 Subversion 的 其 他 提 交 SVN 服 务 器 信 息 可 以 通 过 运 行 git svn info 得 到 与 svn info 相 同 种 类 的 信 息 $ git svn info Path:. URL: Repository Root: Repository UUID: 4c93b f-11de-be05-5f7a Revision: 87 Node Kind: directory Schedule: normal Last Changed Author: schacon Last Changed Rev: 87 Last Changed Date: :07: (Sat, 02 May 2009) 这 就 像 是 在 你 上 一 次 和 Subversion 服 务 器 通 讯 时 同 步 了 之 后, 离 线 运 行 的 blame 与 log 命 令 忽 略 Subversion 所 忽 略 的 如 果 克 隆 一 个 在 任 意 一 处 设 置 svn:ignore 属 性 的 Subversion 仓 库 时, 你 也 许 会 想 要 设 置 对 应 的.gitignore 文 件, 这 样 就 不 会 意 外 的 提 交 那 些 不 该 提 交 的 文 件 git svn 有 两 个 命 令 来 帮 助 解 决 这 个 问 题 第 一 个 是 git svn create-ignore, 它 会 为 你 自 动 地 创 建 对 应 的.gitignore 文 件, 这 样 你 的 下 次 提 交 就 能 包 含 它 们 第 二 个 命 令 是 git svn show-ignore, 它 会 将 你 需 要 放 在.gitignore 文 件 中 的 每 行 内 容 打 印 到 标 准 输 出, 这 样 就 可 以 将 输 出 内 容 重 定 向 到 项 目 的 例 外 文 件 中 : $ git svn show-ignore >.git/info/exclude 这 样, 你 就 不 会 由 于.gitignore 文 件 而 把 项 目 弄 乱 当 你 是 Subversion 团 队 中 唯 一 的 Git 用 户 时 这 是 一 个 好 的 选 项, 并 且 你 的 队 友 并 不 想 要 项 目 内 存 在.gitignore 文 件 Git-Svn 总 结 当 你 不 得 不 使 用 Subversion 服 务 器 或 者 其 他 必 须 运 行 一 个 Subversion 服 务 器 的 开 发 环 境 时,git svn 工 具 很 有 用 你 应 该 把 它 当 做 一 个 不 完 全 的 Git, 然 而, 你 要 是 不 用 它 的 话, 就 会 在 做 转 换 的 过 程 中 遇 到 很 多 麻 烦 的 问 题 为 了 不 惹 麻 烦, 尽 量 遵 守 这 些 准 则 : 保 持 一 个 线 性 的 Git 历 史, 其 中 不 能 有 git merge 生 成 的 合 并 提 交 把 你 在 主 线 分 支 外 开 发 的 全 部 工 作 变 基 到 主 线 分 支 ; 而 不 要 合 并 入 主 线 分 支 不 要 建 立 一 个 单 独 的 Git 服 务 器, 也 不 要 在 Git 服 务 器 上 协 作 可 以 用 一 台 Git 服 务 器 来 帮 助 新 来 的 开 发 者

371 加 速 克 隆, 但 是 不 要 推 送 任 何 不 包 含 git-svn-id 条 目 的 东 西 你 可 能 会 需 要 增 加 一 个 pre-receive 钩 子 来 检 查 每 一 个 提 交 信 息 是 否 包 含 git-svn-id 并 且 拒 绝 任 何 未 包 含 的 提 交 如 果 你 遵 守 了 那 些 准 则, 忍 受 用 一 个 Subversion 服 务 器 来 工 作 可 以 更 容 易 些 然 而, 如 果 有 可 能 迁 移 到 一 个 真 正 的 Git 服 务 器, 那 么 迁 移 过 去 能 使 你 的 团 队 获 得 更 多 好 处 Git 与 Mercurial DVCS 的 宇 宙 里 不 只 有 Git 实 际 上, 在 这 个 空 间 里 有 许 多 其 他 的 系 统 对 于 如 何 正 确 地 进 行 分 布 式 版 本 管 理, 每 一 个 系 统 都 有 自 己 的 视 角 除 了 Git, 最 流 行 的 就 是 Mercurial, 并 且 它 们 两 个 在 很 多 方 面 都 很 相 似 好 消 息 是, 如 果 你 更 喜 欢 Git 的 客 户 端 行 为 但 是 工 作 在 源 代 码 由 Mercurial 控 制 的 项 目 中, 有 一 种 使 用 Git 作 为 Mercurial 托 管 仓 库 的 客 户 端 的 方 法 由 于 Git 与 服 务 器 仓 库 是 使 用 远 程 交 互 的, 那 么 由 远 程 助 手 实 现 的 桥 接 方 法 就 不 会 让 人 很 惊 讶 这 个 项 目 的 名 字 是 git-remote-hg, 可 以 在 找 到 git-remote-hg 首 先, 需 要 安 装 git-remote-hg 实 际 上 需 要 将 它 的 文 件 放 在 PATH 变 量 的 某 个 目 录 中, 像 这 样 : $ curl -o ~/bin/git-remote-hg \ $ chmod +x ~/bin/git-remote-hg 假 定 ~/bin 在 $PATH 变 量 中 Git-remote-hg 有 一 个 其 他 的 依 赖 :mercurial Python 库 如 果 已 经 安 装 了 Python, 安 装 它 就 像 这 样 简 单 : $ pip install mercurial ( 如 果 未 安 装 Python, 访 问 来 获 取 它 ) 需 要 做 的 最 后 一 件 事 是 安 装 Mercurial 客 户 端 如 果 还 没 有 安 装 的 话 请 访 问 来 安 装 现 在 已 经 准 备 好 摇 滚 了 你 所 需 要 的 一 切 就 是 一 个 你 可 以 推 送 的 Mercurial 仓 库 很 幸 运, 每 一 个 Mercurial 仓 库 都 可 以 这 样 做, 所 以 我 们 只 需 要 使 用 大 家 用 来 学 习 Mercurial 的 hello world 仓 库 就 可 以 了 : $ hg clone /tmp/hello

372 开 始 既 然 有 一 个 可 用 的 server-side 仓 库, 我 们 可 以 通 过 一 个 典 型 的 工 作 流 来 了 解 你 将 会 看 到, 这 两 种 系 统 非 常 相 似, 没 有 太 多 的 出 入 和 Git 一 样, 首 先 我 们 克 隆 : $ git clone hg::/tmp/hello /tmp/hello-git $ cd /tmp/hello-git $ git log --oneline --graph --decorate * ac7955c (HEAD, origin/master, origin/branches/default, origin/head, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile * 65bb417 Create a standard "hello, world" program 你 会 注 意 到 与 Mercurial 仓 库 工 作 时 使 用 了 标 准 的 git clone 命 令 那 是 因 为 git-remote-hg 工 作 在 相 当 低 的 底 层, 使 用 类 似 于 Git HTTP/S 协 议 的 机 制 实 现 的 ( 远 程 助 手 ) 由 于 Git 与 Mercurial 都 设 计 为 每 一 个 客 户 端 有 一 个 仓 库 的 完 整 历 史, 所 以 这 个 命 令 做 了 一 次 完 整 的 克 隆, 包 括 所 有 的 项 目 历 史, 并 且 相 当 快 地 完 成 log 命 令 显 示 了 两 次 提 交, 最 后 一 次 提 交 指 向 了 一 大 堆 引 用 那 说 明 这 其 中 的 一 部 分 实 际 上 并 没 有 在 那 儿 让 我 们 看 看.git 目 录 中 实 际 上 有 什 么 : $ tree.git/refs.git/refs heads master hg origin bookmarks master branches default notes hg remotes origin HEAD tags 9 directories, 5 files Git-remote-hg 尝 试 让 结 构 更 有 Git 风 格, 但 是 在 隐 藏 在 下 面 的 是 它 管 理 两 个 轻 微 不 同 系 统 之 间 概 念 的 映 射 refs/hg 目 录 中 存 储 了 实 际 的 远 程 引 用 例 如,refs/hg/origin/branches/default 是 一 个 包 含 以 ` ac7955c ' 开 始 的 SHA-1 值 的 Git 引 用 文 件, 是 master 所 指 向 的 提 交 所 以 refs/hg 目 录 是 一 种 类 似 refs/remotes/origin 的 替 代 品, 但 是 它 引 入 了 书 签 与 分 支 的 区 别

373 notes/hg 文 件 是 git-remote-hg 如 何 在 Git 的 提 交 散 列 与 Mercurial 变 更 集 ID 之 间 建 立 映 射 的 起 点 让 我 们 来 探 索 一 下 : $ cat notes/hg d4c $ git cat-file -p d4c tree 1781c96... author remote-hg <> committer remote-hg <> Notes for master $ git ls-tree 1781c blob ac9117f... 65bb blob 485e ac7955c... $ git cat-file -p ac9117f 0a04b987be5ae354b710cefeba0e2d9de7ad41a9 所 以 refs/notes/hg 指 向 了 一 个 树, 即 在 Git 对 象 数 据 库 中 的 一 个 有 其 他 对 象 名 字 的 列 表 git ls-tree 输 出 tree 对 象 中 所 有 项 目 的 模 式 类 型 对 象 哈 希 与 文 件 名 如 果 深 入 挖 掘 tree 对 象 中 的 一 个 项 目, 我 们 会 发 现 在 其 中 是 一 个 名 字 为 ac9117f 的 blob 对 象 (master 所 指 向 提 交 的 SHA-1 散 列 值 ), 包 含 内 容 0a04b98 ( 是 default 分 支 指 向 的 Mercurial 变 更 集 的 ID) 好 消 息 是 大 多 数 情 况 下 我 们 不 需 要 关 心 以 上 这 些 典 型 的 工 作 流 程 与 使 用 Git 远 程 仓 库 并 没 有 什 么 不 同 在 我 们 继 续 之 前, 这 里 还 有 一 件 需 要 注 意 的 事 情 : 忽 略 Mercurial 与 Git 使 用 非 常 类 似 的 机 制 实 现 这 个 功 能, 但 是 一 般 来 说 你 不 会 想 要 把 一 个.gitignore 文 件 提 交 到 Mercurial 仓 库 中 幸 运 的 是,Git 有 一 种 方 式 可 以 忽 略 本 地 磁 盘 仓 库 的 文 件, 而 且 Mercurial 格 式 是 与 Git 兼 容 的, 所 以 你 只 需 将 这 个 文 件 拷 贝 过 去 : $ cp.hgignore.git/info/exclude.git/info/exclude 文 件 的 作 用 像 是 一 个.gitignore, 但 是 它 不 包 含 在 提 交 中 工 作 流 程 假 设 我 们 已 经 做 了 一 些 工 作 并 且 在 master 分 支 做 了 几 次 提 交, 而 且 已 经 准 备 将 它 们 推 送 到 远 程 仓 库 这 是 我 们 仓 库 现 在 的 样 子 :

374 $ git log --oneline --graph --decorate * ba04a2a (HEAD, master) Update makefile * d25d16f Goodbye * ac7955c (origin/master, origin/branches/default, origin/head, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile * 65bb417 Create a standard "hello, world" program 我 们 的 master 分 支 领 先 origin/master 分 支 两 个 提 交, 但 是 那 两 个 提 交 只 存 在 于 我 们 的 本 地 机 器 中 让 我 们 看 看 在 同 一 时 间 有 没 有 其 他 人 做 过 什 么 重 要 的 工 作 : $ git fetch From hg::/tmp/hello ac7955c..df85e87 master -> origin/master ac7955c..df85e87 branches/default -> origin/branches/default $ git log --oneline --graph --decorate --all * 7b07969 (refs/notes/hg) Notes for default * d4c1038 Notes for master * df85e87 (origin/master, origin/branches/default, origin/head, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation * ba04a2a (HEAD, master) Update makefile * d25d16f Goodbye / * ac7955c Create a makefile * 65bb417 Create a standard "hello, world" program 因 为 使 用 了 --all 标 记, 我 们 看 到 被 git-remote-hg 内 部 使 用 的 notes 引 用, 但 是 可 以 忽 略 它 们 剩 下 的 部 分 是 我 们 期 望 的 ;origin/master 已 经 前 进 了 一 次 提 交, 同 时 我 们 的 历 史 现 在 分 叉 了 Mercurial 和 我 们 本 章 中 讨 论 的 其 他 系 统 不 一 样, 它 能 够 处 理 合 并, 所 以 我 们 不 需 要 做 任 何 其 他 事 情

375 $ git merge origin/master Auto-merging hello.c Merge made by the 'recursive' strategy. hello.c file changed, 1 insertion(+), 1 deletion(-) $ git log --oneline --graph --decorate * 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master' \ * df85e87 (origin/master, origin/branches/default, origin/head, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation * ba04a2a Update makefile * d25d16f Goodbye / * ac7955c Create a makefile * 65bb417 Create a standard "hello, world" program 完 美 运 行 测 试 然 后 所 有 测 试 都 通 过 了, 所 以 我 们 准 备 将 工 作 共 享 给 团 队 的 其 他 成 员 $ git push To hg::/tmp/hello df85e87..0c64627 master -> master 就 是 这 样! 如 果 你 现 在 查 看 一 下 Mercurial 仓 库, 你 会 发 现 这 样 实 现 了 我 们 所 期 望 的 : $ hg log -G --style compact o 5[tip]:4,2 dc8fa4f932b : ben \ Merge remote-tracking branch 'origin/master' o 4 64f27bcefc : ben Update makefile o 3:1 4256fc29598f : ben 2 7db0b4848b3c : ben / Add some documentation o 1 82e55d328c8c : mpm Create a makefile o 0 0a04b987be5a : mpm Create a standard "hello, world" program

376 序 号 2 的 变 更 集 是 由 Mercurial 生 成 的, 序 号 3 与 序 号 4 的 变 更 集 是 由 git-remote-hg 生 成 的, 通 过 Git 推 送 上 来 的 提 交 分 支 与 书 签 Git 只 有 一 种 类 型 的 分 支 : 当 提 交 生 成 时 移 动 的 一 个 引 用 在 Mercurial 中, 这 种 类 型 的 引 用 叫 作 bookmark, 它 的 行 为 非 常 类 似 于 Git 分 支 Mercurial 的 branch 概 念 则 更 重 量 级 一 些 变 更 集 生 成 时 的 分 支 会 记 录 在 变 更 集 中, 意 味 着 它 会 永 远 地 存 在 于 仓 库 历 史 中 这 个 例 子 描 述 了 一 个 在 develop 分 支 上 的 提 交 : $ hg log -l 1 changeset: 6:8f65e5e02793 branch: develop tag: tip user: Ben Straub <ben@straub.cc> date: Thu Aug 14 20:06: summary: More documentation 注 意 开 头 为 branch 的 那 行 Git 无 法 真 正 地 模 拟 这 种 行 为 ( 并 且 也 不 需 要 这 样 做 ; 两 种 类 型 的 分 支 都 可 以 表 达 为 Git 的 一 个 引 用 ), 但 是 git-remote-hg 需 要 了 解 其 中 的 区 别, 因 为 Mercurial 关 心 创 建 Mercurial 书 签 与 创 建 Git 分 支 一 样 容 易 在 Git 这 边 : $ git checkout -b featurea Switched to a new branch 'featurea' $ git push origin featurea To hg::/tmp/hello * [new branch] featurea -> featurea 这 就 是 所 要 做 的 全 部 在 Mercurial 这 边, 它 看 起 来 像 这 样 :

377 $ hg bookmarks featurea 5:bd5ac26f11f9 $ hg log --style compact 6[tip] 8f65e5e : ben More documentation o 5[featureA]:4,2 bd5ac26f11f : ben \ Merge remote-tracking branch 'origin/master' o aaa6b91f : ben update makefile o 3: c : ben goodbye o 2 f098c7f45c4f : ben / Add some documentation o 1 82e55d328c8c : mpm Create a makefile o 0 0a04b987be5a : mpm Create a standard "hello, world" program 注 意 在 修 订 版 本 5 上 的 新 [featurea] 标 签 在 Git 这 边 这 些 看 起 来 像 是 Git 分 支, 除 了 一 点 : 不 能 从 Git 这 边 删 除 书 签 ( 这 是 远 程 助 手 的 一 个 限 制 ) 你 也 可 以 工 作 在 一 个 重 量 级 的 Mercurial branch: 只 需 要 在 branches 命 名 空 间 内 创 建 一 个 分 支 : $ git checkout -b branches/permanent Switched to a new branch 'branches/permanent' $ vi Makefile $ git commit -am 'A permanent change' $ git push origin branches/permanent To hg::/tmp/hello * [new branch] branches/permanent -> branches/permanent 下 面 是 Mercurial 这 边 的 样 子 :

378 $ hg branches permanent 7:a4529d07aad4 develop 6:8f65e5e02793 default 5:bd5ac26f11f9 (inactive) $ hg log -G o changeset: 7:a4529d07aad4 branch: permanent tag: tip parent: 5:bd5ac26f11f9 user: Ben Straub <ben@straub.cc> date: Thu Aug 14 20:21: summary: A permanent changeset: 6:8f65e5e02793 / branch: develop user: Ben Straub <ben@straub.cc> date: Thu Aug 14 20:06: summary: More documentation o changeset: 5:bd5ac26f11f9 \ bookmark: featurea parent: 4:0434aaa6b91f parent: 2:f098c7f45c4f user: Ben Straub <ben@straub.cc> date: Thu Aug 14 20:02: summary: Merge remote-tracking branch 'origin/master' [...] 分 支 名 字 permanent 记 录 在 序 号 7 的 变 更 集 中 在 Git 这 边, 对 于 其 中 任 何 一 种 风 格 的 分 支 的 工 作 都 是 相 同 的 : 仅 仅 是 正 常 做 的 检 出 提 交 抓 取 合 并 拉 取 与 推 送 还 有 需 要 知 道 的 一 件 事 情 是 Mercurial 不 支 持 重 写 历 史, 只 允 许 添 加 历 史 下 面 是 我 们 的 Mercurial 仓 库 在 交 互 式 的 变 基 与 强 制 推 送 后 的 样 子 :

379 $ hg log --style compact -G o 10[tip] cbc : ben A permanent change o 9 f23e12f939c : ben Add some documentation o 8:1 c16971d : ben goodbye o 7:5 a4529d07aad : ben A permanent 6 8f65e5e : ben / More documentation o 5[featureA]:4,2 bd5ac26f11f : ben \ Merge remote-tracking branch 'origin/master' o aaa6b91f : ben update makefile +---o 3: c : ben goodbye o 2 f098c7f45c4f : ben / Add some documentation o 1 82e55d328c8c : mpm Create a makefile o 0 0a04b987be5a : mpm Create a standard "hello, world" program 变 更 集 8 9 与 10 已 经 被 创 建 出 来 并 且 属 于 permanent 分 支, 但 是 旧 的 变 更 集 依 然 在 那 里 这 会 让 使 用 Mercurial 的 团 队 成 员 非 常 困 惑, 所 以 要 避 免 这 种 行 为 Mercurial 总 结 Git 与 Mercurial 如 此 相 似, 以 至 于 跨 这 两 个 系 统 进 行 工 作 十 分 流 畅 如 果 能 注 意 避 免 改 变 在 你 机 器 上 的 历 史 ( 就 像 通 常 建 议 的 那 样 ), 你 甚 至 并 不 会 察 觉 到 另 一 端 是 Mercurial Git 与 Perforce 在 企 业 环 境 中 Perforce 是 非 常 流 行 的 版 本 管 理 系 统 它 大 概 起 始 于 1995 年, 这 使 它 成 为 了 本 章 中 介 绍 的 最 古 老 的 系 统 就 其 本 身 而 言, 它 设 计 时 带 有 当 时 时 代 的 局 限 性 ; 它 假 定 你 始 终 连 接 到 一 个 单 独 的 中 央 服 务 器, 本 地 磁 盘 只 保 存 一 个 版 本 诚 然, 它 的 功 能 与 限 制 适 合 几 个 特 定 的 问 题, 但 实 际 上, 在 很 多 情 况 下, 将 使 用 Perforce

380 的 项 目 换 做 使 用 Git 会 更 好 如 果 你 决 定 混 合 使 用 Perforce 与 Git 这 里 有 两 种 选 择 第 一 个 我 们 要 介 绍 的 是 Perforce 官 方 制 作 的 Git Fusion 桥 接, 它 可 以 将 Perforce 仓 库 中 的 子 树 表 示 为 一 个 可 读 写 的 Git 仓 库 第 二 个 是 git-p4, 一 个 客 户 端 桥 接 允 许 你 将 Git 作 为 Perforce 的 客 户 端 使 用, 而 不 用 在 Perforce 服 务 器 上 做 任 何 重 新 的 配 置 Git Fusion Perforce 提 供 了 一 个 叫 作 Git Fusion 的 产 品 ( 可 在 获 得 ), 它 将 会 在 服 务 器 这 边 同 步 Perforce 服 务 器 与 Git 仓 库 设 置 针 对 我 们 的 例 子, 我 们 将 会 使 用 最 简 单 的 方 式 安 装 Git Fusion: 下 载 一 个 虚 拟 机 来 运 行 Perforce 守 护 进 程 与 Git Fusion 可 以 从 获 得 虚 拟 机 镜 像, 下 载 完 成 后 将 它 导 入 到 你 最 爱 的 虚 拟 机 软 件 中 ( 我 们 将 会 使 用 VirtualBox) 在 第 一 次 启 动 机 器 后, 它 会 询 问 你 自 定 义 三 个 Linux 用 户 (root perforce 与 git) 的 密 码, 并 且 提 供 一 个 实 例 名 字 来 区 分 在 同 一 网 络 下 不 同 的 安 装 当 那 些 都 完 成 后, 将 会 看 到 这 样 :

381 Figure 146. Git Fusion 虚 拟 机 启 动 屏 幕 应 当 注 意 显 示 在 这 儿 的 IP 地 址, 我 们 将 会 在 后 面 用 到 接 下 来, 我 们 将 会 创 建 一 个 Perforce 用 户 选 择 底 部 的 Login 选 项 并 按 下 回 车 ( 或 者 用 SSH 连 接 到 这 台 机 器 ), 然 后 登 录 为 root 然 后 使 用 这 些 命 令 创 建 一 个 用 户 : $ p4 -p localhost:1666 -u super user -f john $ p4 -p localhost:1666 -u john passwd $ exit 第 一 个 命 令 将 会 打 开 一 个 VI 编 辑 器 来 自 定 义 用 户, 但 是 可 以 通 过 输 入 :wq 并 回 车 来 接 受 默 认 选 项 第 二 个 命 令 将 会 提 示 输 入 密 码 两 次 这 就 是 所 有 我 们 要 通 过 终 端 提 示 符 做 的 事 情, 所 以 现 在 可 以 退 出 当 前 会 话 了 接 下 来 要 做 的 事 就 是 告 诉 Git 不 要 验 证 SSL 证 书 Git Fusion 镜 像 内 置 一 个 证 书, 但 是 域 名 并 不 匹 配 你 的 虚 拟 主 机 的 IP 地 址, 所 以 Git 会 拒 绝 HTTPS 连 接 如 果 要 进 行 永 久 安 装, 查 阅 Perforce Git Fusion 手 册 来 安 装 一 个 不 同 的 证 书 ; 然 而, 对 于 我 们 这 个 例 子 来 说, 这 已 经 足 够 了

382 $ export GIT_SSL_NO_VERIFY=true 现 在 我 们 可 以 测 试 所 有 东 西 是 不 是 正 常 工 作 $ git clone Cloning into 'Talkhouse'... Username for ' john Password for ' remote: Counting objects: 630, done. remote: Compressing objects: 100% (581/581), done. remote: Total 630 (delta 172), reused 0 (delta 0) Receiving objects: 100% (630/630), 1.22 MiB 0 bytes/s, done. Resolving deltas: 100% (172/172), done. Checking connectivity... done. 虚 拟 机 镜 像 自 带 一 个 可 以 克 隆 的 样 例 项 目 这 里 我 们 会 使 用 之 前 创 建 的 john 用 户, 通 过 HTTPS 进 行 克 隆 ;Git 询 问 此 次 连 接 的 凭 证, 但 是 凭 证 缓 存 会 允 许 我 们 跳 过 这 步 之 后 的 任 意 后 续 请 求 Fusion 配 置 一 旦 安 装 了 Git Fusion, 你 会 想 要 调 整 配 置 使 用 你 最 爱 的 Perforce 客 户 端 做 这 件 事 实 际 上 相 当 容 易 ; 只 需 要 映 射 Perforce 服 务 器 上 的 //.git-fusion 目 录 到 你 的 工 作 空 间 文 件 结 构 看 起 来 像 这 样 : $ tree. objects repos [...] trees [...] p4gf_config repos Talkhouse p4gf_config users p4gf_usermap 498 directories, 287 files objects 目 录 被 Git Fusion 内 部 用 来 双 向 映 射 Perforce 对 象 与 Git 对 象, 你 不 必 弄 乱 那 儿 的 任 何 东 西 在 这 个 目 录 中 有 一 个 全 局 的 p4gf_config 文 件, 每 个 仓 库 中 也 会 有 一 份 - 这 些 配 置 文 件 决 定 了 Git Fusion 的 行 为 让 我 们 看 一 下 根 目 录 下 的 文 件 :

383 [repo-creation] charset = utf8 [git-to-perforce] change-owner = author enable-git-branch-creation = yes enable-swarm-reviews = yes enable-git-merge-commits = yes enable-git-submodules = yes preflight-commit = none ignore-author-permissions = no read-permission-check = none git-merge-avoidance-after-change-num = [perforce-to-git] http-url = none ssh-url = none [@features] imports = False chunked-push = False matrix2 = False parallel-push = False [authentication] -case-sensitivity = no 这 里 我 们 并 不 会 深 入 介 绍 这 些 选 项 的 含 义, 但 是 要 注 意 这 是 一 个 INI 格 式 的 文 本 文 件, 就 像 Git 的 配 置 这 个 文 件 指 定 了 全 局 选 项, 但 它 可 以 被 仓 库 特 定 的 配 置 文 件 覆 盖, 像 是 repos/talkhouse/p4gf_config 如 果 打 开 这 个 文 件, 你 会 看 到 有 一 些 与 全 局 默 认 不 同 设 置 的 [@repo] 区 块 你 也 会 看 到 像 下 面 这 样 的 区 块 : [Talkhouse-master] git-branch-name = master view = //depot/talkhouse/main-dev/ 这 是 一 个 Perforce 分 支 与 一 个 Git 分 支 的 映 射 这 个 区 块 可 以 被 命 名 成 你 喜 欢 的 名 字, 只 要 保 证 名 字 是 唯 一 的 即 可 git-branch-name 允 许 你 将 在 Git 下 显 得 笨 重 的 仓 库 路 径 转 换 为 更 友 好 的 名 字 view 选 项 使 用 标 准 视 图 映 射 语 法 控 制 Perforce 文 件 如 何 映 射 到 Git 仓 库 可 以 指 定 一 个 以 上 的 映 射, 就 像 下 面 的 例 子 : [multi-project-mapping] git-branch-name = master view = //depot/project1/main/... project1/... //depot/project2/mainline/... project2/...

384 通 过 这 种 方 式, 如 果 正 常 工 作 空 间 映 射 包 含 对 目 录 结 构 的 修 改, 可 以 将 其 复 制 为 一 个 Git 仓 库 最 后 一 个 我 们 讨 论 的 文 件 是 users/p4gf_usermap, 它 将 Perforce 用 户 映 射 到 Git 用 户, 但 你 可 能 不 会 需 要 它 当 从 一 个 Perforce 变 更 集 转 换 为 一 个 Git 提 交 时,Git Fusion 的 默 认 行 为 是 去 查 找 Perforce 用 户, 然 后 把 邮 箱 地 址 与 全 名 存 储 在 Git 的 author/commiter 字 段 中 当 反 过 来 转 换 时, 默 认 的 行 为 是 根 据 存 储 在 Git 提 交 中 author 字 段 中 的 邮 箱 地 址 来 查 找 Perforce 用 户, 然 后 以 该 用 户 提 交 变 更 集 ( 以 及 权 限 的 应 用 ) 大 多 数 情 况 下, 这 个 行 为 工 作 得 很 好, 但 是 考 虑 下 面 的 映 射 文 件 : john john@example.com "John Doe" john johnny@appleseed.net "John Doe" bob employeex@example.com "Anon X. Mouse" joe employeey@example.com "Anon Y. Mouse" 每 一 行 的 格 式 都 是 <user> < > "<full name>", 创 建 了 一 个 单 独 的 用 户 映 射 前 两 行 映 射 不 同 的 邮 箱 地 址 到 同 一 个 Perforce 用 户 账 户 当 使 用 几 个 不 同 的 邮 箱 地 址 ( 或 改 变 邮 箱 地 址 ) 生 成 Git 提 交 并 且 想 要 让 他 们 映 射 到 同 一 个 Perforce 用 户 时 这 会 很 有 用 当 从 一 个 Perforce 变 更 集 创 建 一 个 Git 提 交 时, 第 一 个 匹 配 Perforce 用 户 的 行 会 被 用 作 Git 作 者 信 息 最 后 两 行 从 创 建 的 Git 提 交 中 掩 盖 了 Bob 与 Joe 的 真 实 名 字 与 邮 箱 地 址 当 你 想 要 将 一 个 内 部 项 目 开 源, 但 不 想 将 你 的 雇 员 目 录 公 布 到 全 世 界 时 这 很 不 错 注 意 邮 箱 地 址 与 全 名 需 要 是 唯 一 的, 除 非 想 要 所 有 的 Git 提 交 都 属 于 一 个 虚 构 的 作 者 工 作 流 程 Perforce Git Fusion 是 在 Perforce 与 Git 版 本 控 制 间 双 向 的 桥 接 让 我 们 看 一 下 在 Git 这 边 工 作 是 什 么 样 的 感 觉 假 定 我 们 在 Jam 项 目 中 使 用 上 述 的 配 置 文 件 映 射 了, 可 以 这 样 克 隆 :

385 $ git clone Cloning into 'Jam'... Username for ' john Password for remote: Counting objects: 2070, done. remote: Compressing objects: 100% (1704/1704), done. Receiving objects: 100% (2070/2070), 1.21 MiB 0 bytes/s, done. remote: Total 2070 (delta 1242), reused 0 (delta 0) Resolving deltas: 100% (1242/1242), done. Checking connectivity... done. $ git branch -a * master remotes/origin/head -> origin/master remotes/origin/master remotes/origin/rel2.1 $ git log --oneline --decorate --graph --all * 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch. * d (HEAD, origin/master, origin/head, master) Upgrade to latest metrowerks on Beos -- the Intel one. * bd2f54a Put in fix for jam's NT handle leak. * c0f29e7 Fix URL in a jam doc * cc644ac Radstone's lynx port. [...] 当 首 次 这 样 做 时, 会 花 费 一 些 时 间 这 里 发 生 的 是 Git Fusion 会 将 在 Perforce 历 史 中 所 有 合 适 的 变 更 集 转 换 为 Git 提 交 这 发 生 在 服 务 器 端 本 地, 所 以 会 相 当 快, 但 是 如 果 有 很 多 历 史, 那 么 它 还 是 会 花 费 一 些 时 间 后 来 的 抓 取 会 做 增 量 转 换, 所 以 会 感 觉 更 像 Git 的 本 地 速 度 如 你 所 见, 我 们 的 仓 库 看 起 来 像 之 前 使 用 过 的 任 何 一 个 Git 仓 库 了 这 里 有 三 个 分 支,Git 已 经 帮 助 创 建 了 一 个 跟 踪 origin/master 的 本 地 master 分 支 让 我 们 做 一 些 工 作, 创 建 几 个 新 提 交 : #... $ git log --oneline --decorate --graph --all * cfd46ab (HEAD, master) Add documentation for new feature * a730d77 Whitespace * d (origin/master, origin/head) Upgrade to latest metrowerks on Beos -- the Intel one. * bd2f54a Put in fix for jam's NT handle leak. [...] 我 们 有 两 个 新 提 交 现 在 我 们 检 查 下 是 否 有 其 他 人 在 工 作 :

386 $ git fetch remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 2), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From d afeb15 master -> origin/master $ git log --oneline --decorate --graph --all * 6afeb15 (origin/master, origin/head) Update copyright * cfd46ab (HEAD, master) Add documentation for new feature * a730d77 Whitespace / * d Upgrade to latest metrowerks on Beos -- the Intel one. * bd2f54a Put in fix for jam's NT handle leak. [...] 看 起 来 有 人 在 工 作! 从 这 个 视 图 来 看 你 并 不 知 道 这 点, 但 是 6afeb15 提 交 确 实 是 使 用 Perforce 客 户 端 创 建 的 从 Git 的 视 角 看 它 仅 仅 只 是 另 一 个 提 交, 准 确 地 说 是 一 个 点 让 我 们 看 看 Perforce 服 务 器 如 何 处 理 一 个 合 并 提 交 : $ git merge origin/master Auto-merging README Merge made by the 'recursive' strategy. README file changed, 1 insertion(+), 1 deletion(-) $ git push Counting objects: 9, done. Delta compression using up to 8 threads. Compressing objects: 100% (9/9), done. Writing objects: 100% (9/9), 917 bytes 0 bytes/s, done. Total 9 (delta 6), reused 0 (delta 0) remote: Perforce: 100% (3/3) Loading commit tree into memory... remote: Perforce: 100% (5/5) Finding child commits... remote: Perforce: Running git fast-export... remote: Perforce: 100% (3/3) Checking commits... remote: Processing will continue even if connection is closed. remote: Perforce: 100% (3/3) Copying changelists... remote: Perforce: Submitting new Git commit objects to Perforce: 4 To 6afeb15..89cba2b master -> master Git 认 为 它 成 功 了 让 我 们 从 Perforce 的 视 角 看 一 下 README 文 件 的 历 史, 使 用 p4v 的 版 本 图 功 能

387 Figure 147. Git 推 送 后 的 Perforce 版 本 图 如 果 你 在 之 前 从 未 看 过 这 个 视 图, 它 似 乎 让 人 困 惑, 但 是 它 显 示 出 了 作 为 Git 历 史 图 形 化 查 看 器 相 同 的 概 念 我 们 正 在 查 看 README 文 件 的 历 史, 所 以 左 上 角 的 目 录 树 只 显 示 那 个 文 件 在 不 同 分 支 的 样 子 右 上 方, 我 们 有 不 同 版 本 文 件 关 系 的 可 视 图, 这 个 可 视 图 的 全 局 视 图 在 右 下 方 视 图 中 剩 余 的 部 分 显 示 出 选 择 版 本 的 详 细 信 息 ( 在 这 个 例 子 中 是 2) 还 要 注 意 的 一 件 事 是 这 个 图 看 起 来 很 像 Git 历 史 中 的 图 Perforce 没 有 存 储 1 和 2 提 交 的 命 名 分 支, 所 以 它 在.git-fusion 目 录 中 生 成 了 一 个 anonymous 分 支 来 保 存 它 这 也 会 在 Git 命 名 分 支 不 对 应 Perforce 命 名 分 支 时 发 生 ( 稍 后 你 可 以 使 用 配 置 文 件 来 映 射 它 们 到 Perforce 分 支 ) 这 些 大 多 数 发 生 在 后 台, 但 是 最 终 结 果 是 团 队 中 的 一 个 人 可 以 使 用 Git, 另 一 个 可 以 使 用 Perforce, 而 所 有 人 都 不 知 道 其 他 人 的 选 择 Git-Fusion 总 结 如 果 你 有 ( 或 者 能 获 得 ) 接 触 你 的 Perforce 服 务 器 的 权 限, 那 么 Git Fusion 是 使 Git 与 Perforce 互 相 交 流 的 很 好 的 方 法 这 里 包 含 了 一 点 配 置, 但 是 学 习 曲 线 并 不 是 很 陡 峭 这 是 本 章 中 其 中 一 个 不 会 出 现 无 法 使 用 Git 全 部 能 力 的 警 告 的 章 节 这 并 不 是 说 扔 给 Perforce 任 何 东 西 都 会 高 兴 - 如 果 你 尝 试 重 写 已 经 推 送 的 历 史,Git Fusion 会 拒 绝 它 - 虽 然 Git Fusion 尽 力 让 你 感 觉 是 原 生 的 你 甚 至 可 以 使 用 Git 子 模 块 ( 尽 管 它 们 对 Perforce 用 户 看 起 来 很 奇 怪 ), 合 并 分 支 ( 在 Perforce 这 边 会 被 记 录 了 一 次 整 合 ) 如 果 不 能 说 服 你 的 服 务 器 管 理 员 设 置 Git Fusion, 依 然 有 一 种 方 式 来 一 起 使 用 这 两 个 工 具 Git-p4 Git-p4 是 Git 与 Perforce 之 间 的 双 向 桥 接 它 完 全 运 行 在 你 的 Git 仓 库 内, 所 以 你 不 需 要 任 何 访 问 Perforce 服

388 务 器 的 权 限 ( 当 然 除 了 用 户 验 证 ) Git-p4 并 不 像 Git Fusion 一 样 灵 活 或 完 整, 但 是 它 允 许 你 在 无 需 修 改 服 务 器 环 境 的 情 况 下, 做 大 部 分 想 做 的 事 情 NOTE 为 了 与 git-p4 一 起 工 作 需 要 在 你 的 PATH 环 境 变 量 中 的 某 个 目 录 中 有 p4 工 具 在 写 这 篇 文 章 的 时 候, 它 可 以 在 免 费 获 得 设 置 出 于 演 示 的 目 的, 我 们 将 会 从 上 面 演 示 的 Git Fusion OVA 运 行 Perforce 服 务 器, 但 是 我 们 会 绕 过 Git Fusion 服 务 器 然 后 直 接 进 行 Perforce 版 本 管 理 为 了 使 用 p4 命 令 行 客 户 端 (git-p4 依 赖 项 ), 你 需 要 设 置 两 个 环 境 变 量 : $ export P4PORT= :1666 $ export P4USER=john 开 始 像 在 Git 中 的 任 何 事 情 一 样, 第 一 个 命 令 就 是 克 隆 : $ git p4 clone //depot/www/live www-shallow Importing from //depot/www/live into www-shallow Initialized empty Git repository in /private/tmp/www-shallow/.git/ Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master 这 样 会 创 建 出 一 种 在 Git 中 名 为 shallow 克 隆 ; 只 有 最 新 版 本 的 Perforce 被 导 入 至 Git; 记 住,Perforce 并 未 被 设 计 成 给 每 一 个 用 户 一 个 版 本 使 用 Git 作 为 Perforce 客 户 端 这 样 就 足 够 了, 但 是 为 了 其 他 目 的 的 话 这 样 可 能 不 够 完 成 之 后, 我 们 就 有 一 个 全 功 能 的 Git 仓 库 : $ cd myproject $ git log --oneline --all --graph --decorate * 70eaf78 (HEAD, p4/master, p4/head, master) Initial import of //depot/www/live/ from the state at revision #head 注 意 有 一 个 p4 远 程 代 表 Perforce 服 务 器, 但 是 其 他 东 西 看 起 来 就 像 是 标 准 的 克 隆 实 际 上, 这 有 一 点 误 导 ; 其 实 远 程 仓 库 并 不 存 在 $ git remote -v

389 在 当 前 仓 库 中 并 不 存 在 任 何 远 程 仓 库 Git-p4 创 建 了 一 些 引 用 来 代 表 服 务 器 的 状 态, 它 们 看 起 来 类 似 git log 显 示 的 远 程 引 用, 但 是 它 们 并 不 被 Git 本 身 管 理, 并 且 你 无 法 推 送 它 们 工 作 流 程 好 了, 让 我 们 开 始 一 些 工 作 假 设 你 已 经 在 一 个 非 常 重 要 的 功 能 上 做 了 一 些 工 作, 然 后 准 备 好 将 它 展 示 给 团 队 中 的 其 他 人 $ git log --oneline --all --graph --decorate * c (HEAD, master) Change page title * c0fb617 Update link * 70eaf78 (p4/master, p4/head) Initial import of //depot/www/live/ from the state at revision #head 我 们 已 经 生 成 了 两 次 新 提 交 并 已 准 备 好 推 送 它 们 到 Perforce 服 务 器 让 我 们 检 查 一 下 今 天 其 他 人 是 否 做 了 一 些 工 作 : $ git p4 sync git p4 sync Performing incremental import into refs/remotes/p4/master git branch Depot paths: //depot/www/live/ Import destination: refs/remotes/p4/master Importing revision (100%) $ git log --oneline --all --graph --decorate * 75cd059 (p4/master, p4/head) Update copyright * c (HEAD, master) Change page title * c0fb617 Update link / * 70eaf78 Initial import of //depot/www/live/ from the state at revision #head 看 起 来 他 们 做 了,master 与 p4/master 已 经 分 叉 了 Perforce 的 分 支 系 统 一 点 也 不 像 Git 的, 所 以 提 交 合 并 提 交 没 有 任 何 意 义 Git-p4 建 议 变 基 你 的 提 交, 它 甚 至 提 供 了 一 个 快 捷 方 式 来 这 样 做 : $ git p4 rebase Performing incremental import into refs/remotes/p4/master git branch Depot paths: //depot/www/live/ No changes to import! Rebasing the current branch onto remotes/p4/master First, rewinding head to replay your work on top of it... Applying: Update link Applying: Change page title index.html file changed, 1 insertion(+), 1 deletion(-)

390 从 输 出 中 可 能 大 概 得 知,git p4 rebase 是 git p4 sync 接 着 git rebase p4/master 的 快 捷 方 式 它 比 那 更 聪 明 一 些, 特 别 是 工 作 在 多 个 分 支 时, 但 这 是 一 个 进 步 现 在 我 们 的 历 史 再 次 是 线 性 的, 我 们 准 备 好 我 们 的 改 动 贡 献 回 Perforce git p4 submit 命 令 会 尝 试 在 p4/master 与 master 之 间 的 每 一 个 Git 提 交 创 建 一 个 新 的 Perforce 修 订 版 本 运 行 它 会 带 我 们 到 最 爱 的 编 辑 器, 文 件 内 容 看 起 来 像 是 这 样 : # A Perforce Change Specification. # # Change: The change number. 'new' on a new changelist. # Date: The date this specification was last modified. # Client: The client on which the changelist was created. Readonly. # User: The user who created the changelist. # Status: Either 'pending' or 'submitted'. Read-only. # Type: Either 'public' or 'restricted'. Default is 'public'. # Description: Comments about the changelist. Required. # Jobs: What opened jobs are to be closed by this changelist. # You may delete jobs from this list. (New changelists only.) # Files: What opened files from the default changelist are to be added # to this changelist. You may delete files from this list. # (New changelists only.) Change: new Client: john_bens-mbp_8487 User: john Status: new Description: Update link Files: //depot/www/live/index.html # edit ######## git author ben@straub.cc does not match your p4 account. ######## Use option --preserve-user to modify authorship. ######## Variable git-p4.skipusernamecheck hides this message. ######## everything below this line is just the diff ####### --- //depot/www/live/index.html :26: /Users/ben/john_bens-mbp_8487/john_bensmbp_8487/depot/www/live/index.html :26: ,7

391 </td> <td valign=top> Source and documentation for -<a href=" +<a href="jam.html"> Jam/MR</a>, a software build tool. </td> 除 了 结 尾 git-p4 给 我 们 的 帮 助 性 的 提 示, 其 它 的 与 你 运 行 p4 submit 后 看 到 的 内 容 大 多 相 同 当 提 交 或 变 更 集 需 要 一 个 名 字 时 git-p4 会 分 别 尝 试 使 用 你 的 Git 与 Perforce 设 置, 但 是 有 些 情 况 下 你 会 想 要 覆 盖 默 认 行 为 例 如, 如 果 你 正 导 入 的 提 交 是 由 没 有 Perforce 用 户 账 户 的 贡 献 者 编 写 的, 你 还 是 会 想 要 最 终 的 变 更 集 看 起 来 像 是 他 们 写 的 ( 而 不 是 你 ) Git-p4 帮 助 性 地 将 Git 的 提 交 注 释 导 入 到 Perforce 变 更 集 的 内 容, 这 样 所 有 我 们 必 须 做 的 就 是 保 存 并 退 出, 两 次 ( 每 次 一 个 提 交 ) 这 会 使 shell 输 出 看 起 来 像 这 样 :

392 $ git p4 submit Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/ Synchronizing p4 checkout file(s) up-to-date. Applying dbac45b Update link //depot/www/live/index.html#4 - opened for edit Change created with 1 open file(s). Submitting change Locking 1 files... edit //depot/www/live/index.html#5 Change submitted. Applying 905ec6a Change page title //depot/www/live/index.html#5 - opened for edit Change created with 1 open file(s). Submitting change Locking 1 files... edit //depot/www/live/index.html#6 Change submitted. All commits applied! Performing incremental import into refs/remotes/p4/master git branch Depot paths: //depot/www/live/ Import destination: refs/remotes/p4/master Importing revision (100%) Rebasing the current branch onto remotes/p4/master First, rewinding head to replay your work on top of it... $ git log --oneline --all --graph --decorate * 775a46f (HEAD, p4/master, p4/head, master) Change page title * 05f1ade Update link * 75cd059 Update copyright * 70eaf78 Initial import of //depot/www/live/ from the state at revision #head 结 果 恰 如 我 们 只 是 做 了 一 次 git push, 就 像 是 应 当 实 际 发 生 的 最 接 近 的 类 比 注 意 在 这 个 过 程 中 每 一 个 Git 提 交 都 会 被 转 化 为 一 个 Perforce 变 更 集 ; 如 果 想 要 将 它 们 压 缩 成 为 一 个 单 独 的 提 交, 可 以 在 运 行 git p4 submit 前 进 行 一 次 交 互 式 变 基 同 样 注 意 的 是 所 有 被 转 化 为 变 更 集 的 提 交 的 SHA-1 校 验 和 都 改 变 了 ; 这 是 因 为 git-p4 在 每 一 个 转 化 的 提 交 增 加 一 行 到 提 交 注 释 结 尾 :

393 $ git log -1 commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145 Author: John Doe Date: Sun Aug 31 10:31: Change page title [git-p4: depot-paths = "//depot/www/live/": change = 12144] 当 尝 试 提 交 一 次 合 并 提 交 时 会 发 生 什 么? 让 我 们 尝 试 一 下 这 是 我 们 可 能 会 遇 到 的 一 种 情 形 : $ git log --oneline --all --graph --decorate * 3be6fd8 (HEAD, master) Correct address * 1dcbf21 Merge remote-tracking branch 'p4/master' \ * c4689fc (p4/master, p4/head) Grammar fix * cbacd0a Table borders: yes please * b4959b6 Trademark / * 775a46f Change page title * 05f1ade Update link * 75cd059 Update copyright * 70eaf78 Initial import of //depot/www/live/ from the state at revision #head Git 与 Perforce 历 史 在 775a46f 后 分 叉 了 Git 这 边 有 两 次 提 交, 然 后 一 次 与 Perforce 头 部 的 合 并 提 交, 还 有 另 一 个 提 交 我 们 将 会 尝 试 提 交 这 些 到 Perforce 那 边 单 独 的 变 更 集 之 上 让 我 们 看 看 如 果 尝 试 这 样 提 交 会 发 生 什 么 : $ git p4 submit -n Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/ Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bensmbp_8487/depot/www/live/ Would apply b4959b6 Trademark cbacd0a Table borders: yes please 3be6fd8 Correct address -n 标 记 是 --dry-run 的 缩 写, 将 会 报 告 如 果 提 交 命 令 真 的 运 行 会 发 生 什 么 这 本 例 中, 它 看 起 来 像 是 我 们 会 创 建 三 个 Perforce 变 更 集, 对 应 着 不 在 Perforce 服 务 器 端 的 三 次 非 合 并 提 交 那 听 起 来 像 是 我 们 想 要 的, 让 我 们 看 看 它 会 如 何 做 :

394 $ git p4 submit [ ] $ git log --oneline --all --graph --decorate * dadbd89 (HEAD, p4/master, p4/head, master) Correct address * 1b79a80 Table borders: yes please * Trademark * c4689fc Grammar fix * 775a46f Change page title * 05f1ade Update link * 75cd059 Update copyright * 70eaf78 Initial import of //depot/www/live/ from the state at revision #head 我 们 的 历 史 变 成 线 性 了, 就 像 在 提 交 前 刚 刚 变 基 过 ( 实 际 上 也 是 这 样 ) 这 意 味 着 你 可 以 在 Git 这 边 自 由 地 创 建 工 作 扔 掉 与 合 并 分 支 而 不 用 害 怕 你 的 历 史 会 变 得 与 Perforce 不 兼 容 如 果 你 可 以 变 基 它, 你 就 可 以 将 它 贡 献 到 Perforce 服 务 器 分 支 如 果 你 的 Perforce 项 目 有 多 个 分 支, 你 并 不 会 不 走 运 ;git-p4 可 以 以 一 种 类 似 Git 的 方 式 来 处 理 那 种 情 况 假 定 你 的 Perforce 仓 库 平 铺 的 时 候 像 这 样 : //depot project main dev 并 且 假 定 你 有 一 个 dev 分 支, 有 一 个 视 图 规 格 像 下 面 这 样 : //depot/project/main/... //depot/project/dev/... Git-p4 可 以 自 动 地 检 测 到 这 种 情 形 并 做 正 确 的 事 情 :

395 $ git p4 clone --detect-branches Importing from into project Initialized empty Git repository in /private/tmp/project/.git/ Importing revision 20 (50%) Importing new branch project/dev Resuming with change 20 Importing revision 22 (100%) Updated branches: main dev $ cd project; git log --oneline --all --graph --decorate * eae77ae (HEAD, p4/master, p4/head, master) main * 10d55fb (p4/project/dev) dev * a43cfae Populate //depot/project/main/... //depot/project/dev/... / * 2b83451 Project init 注 意 在 仓 库 路 径 中 说 明 符 ; 那 会 告 诉 git-p4 不 仅 仅 只 是 克 隆 那 个 子 树 最 新 的 变 更 集, 更 包 括 那 些 路 径 未 接 触 的 所 有 变 更 集 这 有 点 类 似 于 Git 的 克 隆 概 念, 但 是 如 果 你 工 作 在 一 个 具 有 很 长 历 史 的 项 目, 那 么 它 会 花 费 一 段 时 间 --detect-branches 标 记 告 诉 git-p4 使 用 Perforce 的 分 支 规 范 来 映 射 到 Git 的 引 用 中 如 果 这 些 映 射 不 在 Perforce 服 务 器 中 ( 使 用 Perforce 的 一 种 完 美 有 效 的 方 式 ), 你 可 以 告 诉 git-p4 分 支 映 射 是 什 么, 然 后 你 会 得 到 同 样 的 结 果 : $ git init project Initialized empty Git repository in /tmp/project/.git/ $ cd project $ git config git-p4.branchlist main:dev $ git clone --detect-branches //depot/project@all. 设 置 git-p4.branchlist 配 置 选 项 为 main:dev 告 诉 git-p4 那 个 main 与 dev 都 是 分 支, 第 二 个 是 第 一 个 的 子 分 支 如 果 我 们 现 在 运 行 git checkout -b dev p4/project/dev 并 且 做 一 些 提 交, 在 运 行 git p4 submit 时 git-p4 会 聪 明 地 选 择 正 确 的 分 支 不 幸 的 是,git-p4 不 能 混 用 shallow 克 隆 与 多 个 分 支 ; 如 果 你 有 一 个 巨 型 项 目 并 且 想 要 同 时 工 作 在 不 止 一 个 分 支 上, 可 能 不 得 不 针 对 每 一 个 你 想 要 提 交 的 分 支 运 行 一 次 git p4 clone 为 了 创 建 与 整 合 分 支, 你 不 得 不 使 用 一 个 Perforce 客 户 端 Git-p4 只 能 同 步 或 提 交 已 有 分 支, 并 且 它 一 次 只 能 做 一 个 线 性 的 变 更 集 如 果 你 在 Git 中 合 并 两 个 分 支 并 尝 试 提 交 新 的 变 更 集, 所 有 这 些 会 被 记 录 为 一 串 文 件 修 改 ; 关 于 哪 个 分 支 参 与 的 元 数 据 在 整 合 中 会 丢 失 Git 与 Perforce 总 结 Git-p4 将 与 Perforce 服 务 器 工 作 时 使 用 Git 工 作 流 成 为 可 能, 并 且 它 非 常 擅 长 这 点 然 而, 需 要 记 住 的 重 要 一 点 是 Perforce 负 责 源 头, 而 你 只 是 在 本 地 使 用 Git 在 共 享 Git 提 交 时 要 相 当 小 心 : 如 果 你 有 一 个 其 他 人 使 用 的

396 远 程 仓 库, 不 要 在 提 交 到 Perforce 服 务 器 前 推 送 任 何 提 交 如 果 想 要 为 源 码 管 理 自 由 地 混 合 使 用 Perforce 与 Git 作 为 客 户 端, 可 以 说 服 服 务 器 管 理 员 安 装 Git Fusion,Git Fusion 使 Git 作 为 Perforce 服 务 器 的 首 级 版 本 管 理 客 户 端 Git 与 TFS Git 在 Windows 开 发 者 当 中 变 得 流 行 起 来, 如 果 你 正 在 Windows 上 编 写 代 码 并 且 正 在 使 用 Microsoft 的 Team Foundation Server (TFS), 这 会 是 个 好 机 会 TFS 是 一 个 包 含 工 作 项 目 检 测 与 跟 踪 支 持 Scrum 与 其 他 流 程 管 理 方 法 代 码 审 核 版 本 控 制 的 协 作 套 件 这 里 有 一 点 困 惑 :TFS 是 服 务 器, 它 支 持 通 过 Git 与 它 们 自 定 义 的 VCS 来 管 理 源 代 码, 这 被 他 们 称 为 TFVC(Team Foundation Version Control) Git 支 持 TFS( 自 2013 版 本 起 ) 的 部 分 新 功 能, 所 以 在 那 之 前 所 有 工 具 都 将 版 本 控 制 部 分 称 为 TFS, 即 使 实 际 上 他 们 大 部 分 时 间 都 在 与 TFVC 工 作 如 果 发 现 你 的 团 队 在 使 用 TFVC 但 是 你 更 愿 意 使 用 Git 作 为 版 本 控 制 客 户 端, 这 里 为 你 准 备 了 一 个 项 目 选 择 哪 个 工 具 实 际 上, 这 里 有 两 个 工 具 :git-tf 与 git-tfs Git-tfs ( 可 以 在 找 到 ) 是 一 个.NET 项 目, 它 只 能 运 行 在 Windows 上 ( 截 至 文 章 完 成 时 ) 为 了 操 作 Git 仓 库, 它 使 用 了 libgit2 的.NET 绑 定, 一 个 可 靠 的 面 向 库 的 Git 实 现, 十 分 灵 活 且 性 能 优 越 Libgit2 并 不 是 一 个 完 整 的 Git 实 现, 为 了 弥 补 差 距 git-tfs 实 际 上 会 调 用 Git 命 令 行 客 户 端 来 执 行 某 些 操 作, 因 此 在 操 作 Git 仓 库 时 并 没 有 任 何 功 能 限 制 因 为 它 使 用 Visual Studio 程 序 集 对 服 务 器 进 行 操 作, 所 以 它 对 TFVC 的 支 持 非 常 成 熟 这 并 不 意 味 着 你 需 要 接 触 那 些 程 序 集, 但 是 意 味 着 你 需 要 安 装 Visual Studio 的 一 个 最 近 版 本 (2010 之 后 的 任 何 版 本, 包 括 2012 之 后 的 Express 版 本 ), 或 者 Visual Studio SDK Git-tf( 主 页 在 是 一 个 Java 项 目, 因 此 它 可 以 运 行 在 任 何 一 个 有 Java 运 行 时 环 境 的 电 脑 上 它 通 过 JGit( 一 个 Git 的 JVM 实 现 ) 来 与 Git 仓 库 交 互, 这 意 味 着 事 实 上 它 没 有 Git 功 能 上 的 限 制 然 而, 相 对 于 git-tfs 它 对 TFVC 的 支 持 是 有 限 的 - 例 如, 它 不 支 持 分 支 所 以 每 个 工 具 都 有 优 点 和 缺 点, 每 个 工 具 都 有 它 适 用 的 情 况 我 们 在 本 书 中 将 会 介 绍 它 们 两 个 的 基 本 用 法 NOTE 你 需 要 有 一 个 基 于 TFVC 的 仓 库 来 执 行 后 续 的 指 令 现 实 中 它 们 并 没 有 Git 或 Subversion 仓 库 那 样 多, 所 以 你 可 能 需 要 创 建 一 个 你 自 己 的 仓 库 Codeplex ( 或 Visual Studio Online ( 都 是 非 常 好 的 选 择 使 用 :git-tf 和 其 它 任 何 Git 项 目 一 样, 你 要 做 的 第 一 件 事 是 克 隆 使 用 git-tf 克 隆 看 起 来 像 这 样 : $ git tf clone $/myproject/main project_git

397 第 一 个 参 数 是 一 个 TFVC 集 的 URL, 第 二 个 参 数 类 似 于 $/project/branch 的 形 式, 第 三 个 参 数 是 将 要 创 建 的 本 地 Git 仓 库 路 径 ( 最 后 一 项 可 以 省 略 ) Git-tf 同 一 时 间 只 能 工 作 在 一 个 分 支 上 ; 如 果 你 想 要 检 入 一 个 不 同 的 TFVC 分 支, 你 需 要 从 那 个 分 支 克 隆 一 份 新 的 这 会 创 建 一 个 完 整 功 能 的 Git 仓 库 : $ cd project_git $ git log --all --oneline --decorate 512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message 这 叫 做 浅 克 隆, 意 味 着 只 下 载 了 最 新 的 变 更 集 TFVC 并 未 设 计 成 为 每 一 个 客 户 端 提 供 一 份 全 部 历 史 记 录 的 拷 贝, 所 以 git-tf 默 认 行 为 是 获 得 最 新 的 版 本, 这 样 更 快 一 些 如 果 愿 意 多 花 一 些 时 间, 使 用 --deep 选 项 克 隆 整 个 项 目 历 史 可 能 更 有 价 值 $ git tf clone $/myproject/main \ project_git --deep Username: domain\user Password: Connecting to TFS... Cloning $/myproject into /tmp/project_git: 100%, done. Cloned 4 changesets. Cloned last changeset as d44b17a $ cd project_git $ git log --all --oneline --decorate d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye 126aa7b (tag: TFS_C35189) 8f77431 (tag: TFS_C35178) FIRST 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \ Team Project Creation Wizard 注 意 名 字 类 似 TFS_C35189 的 标 签 ; 这 是 一 个 帮 助 你 知 道 Git 提 交 与 TFVC 变 更 集 关 联 的 功 能 这 是 一 种 优 雅 的 表 示 方 式, 因 为 通 过 一 个 简 单 的 log 命 令 就 可 以 看 到 你 的 提 交 是 如 何 与 TFVC 中 已 存 在 快 照 关 联 起 来 的 它 们 并 不 是 必 须 的 ( 并 且 实 际 上 可 以 使 用 git config git-tf.tag false 来 关 闭 它 们 )- git-tf 会 在.git/gittf 文 件 中 保 存 真 正 的 提 交 与 变 更 集 的 映 射 使 用 :git-tfs Git-tfs 克 隆 行 为 略 为 不 同 观 察 :

398 PS> git tfs clone --with-branches \ \ $/project/trunk project_git Initialized empty Git repository in C:/Users/ben/project_git/.git/ C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9 C16 = c403405f4989d73a2c3c119e79021cb2104ce44a Tfs branches found: - $/tfvc-test/featurea The name of the local branch will be : featurea C17 = d202b53f67bde32171d c644e562f1c439 C18 = 44cd729d8df868a8be20438fdeeefb961958b674 注 意 --with-branches 选 项 Git-tfs 能 够 映 射 TFVC 分 支 到 Git 分 支, 这 个 标 记 告 诉 它 为 每 一 个 TFVC 分 支 建 立 一 个 本 地 的 Git 分 支 强 烈 推 荐 曾 经 在 TFS 中 新 建 过 分 支 或 合 并 过 分 支 的 仓 库 使 用 这 个 标 记, 但 是 如 果 使 用 的 服 务 器 的 版 本 比 TFS 2010 更 老 - 在 那 个 版 本 前, 分 支 只 是 文 件 夹, 所 以 git-tfs 无 法 将 它 们 与 普 通 文 件 夹 区 分 开 让 我 们 看 一 下 最 终 的 Git 仓 库 : PS> git log --oneline --graph --decorate --all * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk * c (HEAD, tfs/default, master) Hello * b75da1a New project PS> git log -1 commit c403405f4989d73a2c3c119e79021cb2104ce44a Author: Ben Straub <ben@straub.cc> Date: Fri Aug 1 03:41: Hello git-tfs-id: [ 有 两 个 本 地 分 支,master 与 featurea, 分 别 代 表 着 克 隆 (TFVC 中 的 Trunk) 与 子 分 支 (TFVC 中 的 featurea) 的 初 始 状 态 也 可 以 看 到 tfs remote 也 有 一 对 引 用 :default 与 featurea, 代 表 TFVC 分 支 Git-tfs 映 射 从 tfs/default 克 隆 的 分 支, 其 他 的 会 有 它 们 自 己 的 名 字 另 一 件 需 要 注 意 的 事 情 是 在 提 交 信 息 中 的 git-tfs-id: 行 Git-tfs 使 用 这 些 标 记 而 不 是 标 签 来 关 联 TFVC 变 更 集 与 Git 提 交 有 一 个 潜 在 的 问 题 是 Git 提 交 在 推 送 到 TFVC 前 后 会 有 不 同 的 SHA-1 校 验 和 Git-tf[s] 工 作 流 程

399 无 论 你 使 用 哪 个 工 具, 都 需 要 先 设 置 几 个 Git 配 置 选 项 来 避 免 一 些 问 题 NOTE $ git config set --local core.ignorecase=true $ git config set --local core.autocrlf=false 显 然, 接 下 来 要 做 的 事 情 就 是 要 在 项 目 中 做 一 些 工 作 TFVC 与 TFS 有 几 个 功 能 可 能 会 增 加 你 的 工 作 流 程 的 复 杂 性 : 1. TFVC 无 法 表 示 特 性 分 支, 这 会 增 加 一 点 复 杂 度 这 会 导 致 需 要 以 非 常 不 同 的 方 式 使 用 TFVC 与 Git 表 示 的 分 支 2. 要 意 识 到 TFVC 允 许 用 户 从 服 务 器 上 检 出 文 件 并 锁 定 它 们, 这 样 其 他 人 就 无 法 编 辑 了 显 然 它 不 会 阻 止 你 在 本 地 仓 库 中 编 辑 它 们, 但 是 当 推 送 你 的 修 改 到 TFVC 服 务 器 时 会 出 现 问 题 3. TFS 有 一 个 封 闭 检 入 的 概 念,TFS 构 建 - 测 试 循 环 必 须 在 检 入 被 允 许 前 成 功 完 成 这 使 用 了 TFVC 的 shelve 功 能, 我 们 不 会 在 这 里 详 述 可 以 通 过 git-tf 手 动 地 模 拟 这 个 功 能, 并 且 git-tfs 提 供 了 封 闭 敏 感 的 checkintool 命 令 出 于 简 洁 性 的 原 因, 我 们 这 里 介 绍 的 是 一 种 轻 松 的 方 式, 回 避 并 避 免 了 大 部 分 问 题 工 作 流 程 :git-tf 假 定 你 完 成 了 一 些 工 作, 在 master 中 做 了 几 次 Git 提 交, 然 后 准 备 将 你 的 进 度 共 享 到 服 务 器 这 是 我 们 的 Git 仓 库 : $ git log --oneline --graph --decorate --all * 4178a82 (HEAD, master) update code * 9df2ae3 update readme * d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye * 126aa7b (tag: TFS_C35189) * 8f77431 (tag: TFS_C35178) FIRST * 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \ Team Project Creation Wizard 我 们 想 要 拿 到 在 4178a82 提 交 的 快 照 并 将 其 推 送 到 TFVC 服 务 器 先 说 重 要 的 : 让 我 们 看 看 自 从 上 次 连 接 后 我 们 的 队 友 是 否 进 行 过 改 动 :

400 $ git tf fetch Username: domain\user Password: Connecting to TFS... Fetching $/myproject at latest changeset: 100%, done. Downloaded changeset as commit 8ef06a8. Updated FETCH_HEAD. $ git log --oneline --graph --decorate --all * 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text * 4178a82 (HEAD, master) update code * 9df2ae3 update readme / * d44b17a (tag: TFS_C35190) Goodbye * 126aa7b (tag: TFS_C35189) * 8f77431 (tag: TFS_C35178) FIRST * 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \ Team Project Creation Wizard 看 起 来 其 他 人 也 做 了 一 些 改 动, 现 在 我 们 有 一 个 分 叉 的 历 史 这 就 是 Git 的 优 势, 但 是 我 们 现 在 有 两 种 处 理 的 方 式 : 1. 像 一 名 Git 用 户 一 样 自 然 的 生 成 一 个 合 并 提 交 ( 毕 竟, 那 也 是 git pull 做 的 ),git-tf 可 以 通 过 一 个 简 单 的 git tf pull 来 帮 你 完 成 然 而, 我 们 要 注 意 的 是,TFVC 却 并 不 这 样 想, 如 果 你 推 送 合 并 提 交 那 么 你 的 历 史 在 两 边 看 起 来 都 不 一 样, 这 会 造 成 困 惑 其 次, 如 果 你 计 划 将 所 有 你 的 改 动 提 交 为 一 次 变 更 集, 这 可 能 是 最 简 单 的 选 择 2. 变 基 使 我 们 的 提 交 历 史 变 成 直 线, 这 意 味 着 我 们 有 个 选 项 可 以 将 我 们 的 每 一 个 Git 提 交 转 换 为 一 个 TFVC 变 更 集 因 为 这 种 方 式 为 其 他 选 项 留 下 了 可 能, 所 以 我 们 推 荐 你 这 样 做 ;git-tf 可 以 很 简 单 地 通 过 git tf pull --rebase 帮 你 达 成 目 标 这 是 你 的 选 择 在 本 例 中, 我 们 会 进 行 变 基 :

401 $ git rebase FETCH_HEAD First, rewinding head to replay your work on top of it... Applying: update readme Applying: update code $ git log --oneline --graph --decorate --all * 5a0e25e (HEAD, master) update code * 6eb3eb5 update readme * 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text * d44b17a (tag: TFS_C35190) Goodbye * 126aa7b (tag: TFS_C35189) * 8f77431 (tag: TFS_C35178) FIRST * 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \ Team Project Creation Wizard 现 在 我 们 准 备 好 生 成 一 个 检 入 来 推 送 到 TFVC 服 务 器 上 了 Git-tf 给 你 一 个 将 自 上 次 修 改 ( 即 --shallow 选 项, 默 认 启 用 ) 以 来 所 有 的 修 改 生 成 的 一 个 单 独 的 变 更 集 以 及 为 每 一 个 Git 提 交 (--deep) 生 成 的 一 个 新 的 变 更 集 在 本 例 中, 我 们 将 会 创 建 一 个 变 更 集 : $ git tf checkin -m 'Updating readme and code' Username: domain\user Password: Connecting to TFS... Checking in to $/myproject: 100%, done. Checked commit 5a0e25e in as changeset $ git log --oneline --graph --decorate --all * 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code * 6eb3eb5 update readme * 8ef06a8 (tag: TFS_C35320) just some text * d44b17a (tag: TFS_C35190) Goodbye * 126aa7b (tag: TFS_C35189) * 8f77431 (tag: TFS_C35178) FIRST * 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \ Team Project Creation Wizard 那 有 一 个 新 标 签 TFS_C35348, 表 明 TFVC 已 经 存 储 了 一 个 相 当 于 5a0e25e 提 交 的 快 照 要 重 点 注 意 的 是, 不 是 每 一 个 Git 提 交 都 需 要 在 TFVC 中 存 在 一 个 相 同 的 副 本 ; 例 如 6eb3eb5 提 交, 在 服 务 器 上 并 不 存 在 这 就 是 主 要 的 工 作 流 程 有 一 些 你 需 要 考 虑 的 其 他 注 意 事 项 : 没 有 分 支 Git-tf 同 一 时 间 只 能 从 一 个 TFVC 分 支 创 建 一 个 Git 仓 库 协 作 时 使 用 TFVC 或 Git, 而 不 是 两 者 同 时 使 用 同 一 个 TFVC 仓 库 的 不 同 git-tf 克 隆 会 有 不 同 的 SHA-1 校 验 和, 这 会 导 致 无 尽 的 头 痛 问 题

402 如 果 你 的 团 队 的 工 作 流 程 包 括 在 Git 中 协 作 并 定 期 与 TFVC 同 步, 只 能 使 用 其 中 的 一 个 Git 仓 库 连 接 到 TFVC 工 作 流 程 :git-tfs 让 我 们 使 用 git-tfs 来 走 一 遍 同 样 的 情 景 这 是 我 们 在 Git 仓 库 中 master 分 支 上 生 成 的 几 个 新 提 交 : PS> git log --oneline --graph --all --decorate * c3bd3ae (HEAD, master) update code * d85e5a2 update readme * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk / * c (tfs/default) Hello * b75da1a New project 让 我 们 看 一 下 在 我 们 工 作 时 有 没 有 人 完 成 一 些 其 它 的 工 作 : PS> git tfs fetch C19 = aea74a0313de0a391940c999e51c5c15c381d91d PS> git log --all --oneline --graph --decorate * aea74a0 (tfs/default) update documentation * c3bd3ae (HEAD, master) update code * d85e5a2 update readme / * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk / * c Hello * b75da1a New project 是 的, 那 说 明 我 们 的 同 事 增 加 了 一 个 新 的 TFVC 变 更 集, 显 示 为 新 的 aea74a0 提 交, 而 tfs/default 远 程 分 支 已 经 被 移 除 了 与 git-tf 相 同, 我 们 有 两 种 基 础 选 项 来 解 决 这 个 分 叉 历 史 问 题 : 1. 通 过 变 基 来 保 持 历 史 是 线 性 的 2. 通 过 合 并 来 保 留 改 动 在 本 例 中, 我 们 将 要 做 一 个 深 检 入, 也 就 是 说 每 一 个 Git 提 交 会 变 成 一 个 TFVC 变 更 集, 所 以 我 们 想 要 变 基

403 PS> git rebase tfs/default First, rewinding head to replay your work on top of it... Applying: update readme Applying: update code PS> git log --all --oneline --graph --decorate * 10a75ac (HEAD, master) update code * 5cec4ab update readme * aea74a0 (tfs/default) update documentation * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk / * c Hello * b75da1a New project 现 在 已 经 准 备 好 通 过 检 入 我 们 的 代 码 到 TFVC 服 务 器 来 完 成 贡 献 我 们 这 里 将 会 使 用 rcheckin 命 令 将 HEAD 到 第 一 个 tfs 远 程 分 支 间 的 每 一 个 Git 提 交 转 换 为 一 个 TFVC 变 更 集 (checkin 命 令 只 会 创 建 一 个 变 更 集, 有 些 类 似 于 压 缩 Git 提 交 ) PS> git tfs rcheckin Working with tfs remote: default Fetching changes from TFS to minimize possibility of late conflict... Starting checkin of 5cec4ab4 'update readme' add README.md C20 = 71a5ddce274c19f8fdc322b4f165d93d Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit... Rebase done successfully. Starting checkin of b1bf0f99 'update code' edit.git\tfs\default\workspace\consoleapplication1/consoleapplication1/program.cs C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit... Rebase done successfully. No more to rcheckin. PS> git log --all --oneline --graph --decorate * ff04e7c (HEAD, tfs/default, master) update code * 71a5ddc update readme * aea74a0 update documentation * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk / * c Hello * b75da1a New project

404 注 意 在 每 次 成 功 检 入 到 TFVC 服 务 器 后,git-tfs 是 如 何 将 剩 余 的 工 作 变 基 到 服 务 器 上 这 是 因 为 它 将 git-tfsid 属 性 加 入 到 提 交 信 息 的 底 部, 这 将 会 改 变 SHA-1 校 验 和 这 恰 恰 是 有 意 设 计 的, 没 有 什 么 事 情 可 以 担 心 了, 但 是 你 应 该 意 识 到 发 生 了 什 么, 特 别 是 当 你 想 要 与 其 他 人 共 享 Git 提 交 时 TFS 有 许 多 与 它 的 版 本 管 理 系 统 整 合 的 功 能, 比 如 工 作 项 目 指 定 审 核 者 封 闭 检 入 等 等 仅 仅 通 过 命 令 行 工 具 使 用 这 些 功 能 来 工 作 是 很 笨 重 的, 但 是 幸 运 的 是 git-tfs 允 许 你 轻 松 地 运 行 一 个 图 形 化 的 检 入 工 具 : PS> git tfs checkintool PS> git tfs ct 它 看 起 来 有 点 像 这 样 : Figure 148. git-tfs 检 入 工 具 对 TFS 用 户 来 说 这 看 起 来 很 熟 悉, 因 为 它 就 是 从 Visual Studio 中 运 行 的 同 一 个 窗 口 Git-tfs 同 样 允 许 你 从 你 的 Git 仓 库 控 制 TFVC 分 支 如 同 这 个 例 子, 让 我 们 创 建 一 个 :

405 PS> git tfs branch $/tfvc-test/featurebee The name of the local branch will be : featurebee C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5 PS> git log --oneline --graph --decorate --all * 1d54865 (tfs/featurebee) Creation branch $/myproject/featurebee * ff04e7c (HEAD, tfs/default, master) update code * 71a5ddc update readme * aea74a0 update documentation * 44cd729 (tfs/featurea, featurea) Goodbye * d202b53 Branched from $/tfvc-test/trunk / * c Hello * b75da1a New project 在 TFVC 中 创 建 一 个 分 支 意 味 着 增 加 一 个 使 分 支 存 在 的 变 更 集, 这 会 映 射 为 一 个 Git 提 交 也 要 注 意 的 是 git-tfs 创 建 了 tfs/featurebee 远 程 分 支, 但 是 HEAD 始 终 指 向 master 如 果 你 想 要 在 新 生 成 的 分 支 上 工 作, 那 你 也 许 应 该 通 过 从 那 次 提 交 创 建 一 个 特 性 分 支 的 方 式 使 你 新 的 提 交 基 于 1d54865 提 交 Git 与 TFS 总 结 Git-tf 与 Git-tfs 都 是 与 TFVC 服 务 器 交 互 的 很 好 的 工 具 它 们 允 许 你 在 本 地 使 用 Git 的 能 力, 避 免 与 中 央 TFVC 服 务 器 频 繁 交 流, 使 你 做 为 一 个 开 发 者 的 生 活 更 轻 松, 而 不 用 强 制 整 个 团 队 迁 移 到 Git 如 果 你 在 Windows 上 工 作 ( 那 很 有 可 能 你 的 团 队 正 在 使 用 TFS), 你 可 能 会 想 要 使 用 git-tfs, 因 为 它 的 功 能 更 完 整, 但 是 如 果 你 在 其 他 平 台 工 作, 你 只 能 使 用 略 有 限 制 的 git-tf 像 本 章 中 大 多 数 工 具 一 样, 你 应 当 使 用 其 中 的 一 个 版 本 系 统 作 为 主 要 的, 而 使 用 另 一 个 做 为 次 要 的 - 不 管 是 Git 还 是 TFVC 都 可 以 做 为 协 作 中 心, 但 不 是 两 者 都 用 迁 移 到 Git 如 果 你 现 在 有 一 个 正 在 使 用 其 他 VCS 的 代 码 库, 但 是 你 已 经 决 定 开 始 使 用 Git, 必 须 通 过 某 种 方 式 将 你 的 项 目 迁 移 至 Git 这 一 部 分 会 介 绍 一 些 通 用 系 统 的 导 入 器, 然 后 演 示 如 何 开 发 你 自 己 定 制 的 导 入 器 你 将 会 学 习 如 何 从 几 个 大 型 专 业 应 用 的 SCM 系 统 中 导 入 数 据, 不 仅 因 为 它 们 是 大 多 数 想 要 转 换 的 用 户 正 在 使 用 的 系 统, 也 因 为 获 取 针 对 它 们 的 高 质 量 工 具 很 容 易 Subversion 如 果 你 阅 读 过 前 面 关 于 git svn 的 章 节, 可 以 轻 松 地 使 用 那 些 指 令 来 git svn clone 一 个 仓 库, 停 止 使 用 Subversion 服 务 器, 推 送 到 一 个 新 的 Git 服 务 器, 然 后 就 可 以 开 始 使 用 了 如 果 你 想 要 历 史, 可 以 从 Subversion 服 务 器 上 尽 可 能 快 地 拉 取 数 据 来 完 成 这 件 事 ( 这 可 能 会 花 费 一 些 时 间 ) 然 而, 导 入 并 不 完 美 ; 因 为 花 费 太 长 时 间 了, 你 可 能 早 已 用 其 他 方 法 完 成 导 入 操 作 导 入 产 生 的 第 一 个 问 题 就 是 作 者 信 息 在 Subversion 中, 每 一 个 人 提 交 时 都 需 要 在 系 统 中 有 一 个 用 户, 它 会 被 记 录 在 提 交 信 息 内 在 之 前 章 节 的 例 子 中 几 个 地 方 显 示 了 schacon, 比 如 blame 输 出 与 git svn log 如 果 想 要 将 上 面 的 Subversion 用 户 映 射 到 一 个 更 好 的 Git 作 者 数 据 中, 你 需 要 一 个 Subversion 用 户 到 Git 用 户 的 映 射 创 建 一 个 users.txt 的 文 件 包 含 像 下 面 这 种 格 式 的 映 射 :

406 schacon = Scott Chacon <schacon@ge .com> selse = Someo Nelse <selse@ge .com> 为 了 获 得 SVN 使 用 的 作 者 名 字 列 表, 可 以 运 行 这 个 : $ svn log --xml grep author sort -u \ perl -pe 's/.*>(.*?)<.*/$1 = /' 这 会 将 日 志 输 出 为 XML 格 式, 然 后 保 留 作 者 信 息 行 去 除 重 复 去 除 XML 标 记 ( 很 显 然 这 只 会 在 安 装 了 grep sort 与 perl 的 机 器 上 运 行 ) 然 后, 将 输 出 重 定 向 到 你 的 users.txt 文 件 中, 这 样 就 可 以 在 每 一 个 记 录 后 面 加 入 对 应 的 Git 用 户 数 据 你 可 以 将 此 文 件 提 供 给 git svn 来 帮 助 它 更 加 精 确 地 映 射 作 者 数 据 也 可 以 通 过 传 递 --no-metadata 给 clone 与 init 命 令, 告 诉 git svn 不 要 包 括 Subversion 通 常 会 导 入 的 元 数 据 这 会 使 你 的 import 命 令 看 起 来 像 这 样 : $ git svn clone \ --authors-file=users.txt --no-metadata -s my_project 现 在 在 my_project 目 录 中 应 当 有 了 一 个 更 好 的 Subversion 导 入 并 不 像 是 下 面 这 样 的 提 交 : commit 37efa680e8473b615de980fa a35a Author: schacon <schacon@4c93b f-11de-be05-5f7a > Date: Sun May 3 00:12: fixed install - go to trunk git-svn-id: 4c93b f-11debe05-5f7a 反 而 它 们 看 起 来 像 是 这 样 : commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2 Author: Scott Chacon <schacon@ge .com> Date: Sun May 3 00:12: fixed install - go to trunk 不 仅 是 Author 字 段 更 好 看 了,git-svn-id 也 不 在 了

407 之 后, 你 应 当 做 一 些 导 入 后 的 清 理 工 作 第 一 步, 你 应 当 清 理 git svn 设 置 的 奇 怪 的 引 用 首 先 移 动 标 签, 这 样 它 们 就 是 标 签 而 不 是 奇 怪 的 远 程 引 用, 然 后 你 会 移 动 剩 余 的 分 支 这 样 它 们 就 是 本 地 的 了 为 了 将 标 签 变 为 合 适 的 Git 标 签, 运 行 $ cp -Rf.git/refs/remotes/origin/tags/*.git/refs/tags/ $ rm -Rf.git/refs/remotes/origin/tags 这 会 使 原 来 在 remotes/origin/tags/ 里 的 远 程 分 支 引 用 变 成 真 正 的 ( 轻 量 ) 标 签 接 下 来, 将 refs/remotes 下 剩 余 的 引 用 移 动 为 本 地 分 支 : $ cp -Rf.git/refs/remotes/*.git/refs/heads/ $ rm -Rf.git/refs/remotes 现 在 所 有 的 旧 分 支 都 是 真 正 的 Git 分 支, 并 且 所 有 的 旧 标 签 都 是 真 正 的 Git 标 签 最 后 一 件 要 做 的 事 情 是, 将 你 的 新 Git 服 务 器 添 加 为 远 程 仓 库 并 推 送 到 上 面 下 面 是 一 个 将 你 的 服 务 器 添 加 为 远 程 仓 库 的 例 子 : $ git remote add origin git@my-git-server:myrepository.git 因 为 想 要 上 传 所 有 分 支 与 标 签, 你 现 在 可 以 运 行 : $ git push origin --all 通 过 以 上 漂 亮 干 净 地 导 入 操 作, 你 的 所 有 分 支 与 标 签 都 应 该 在 新 Git 服 务 器 上 Mercurial 因 为 Mercurial 与 Git 在 表 示 版 本 时 有 着 非 常 相 似 的 模 型, 也 因 为 Git 拥 有 更 加 强 大 的 灵 活 性, 将 一 个 仓 库 从 Mercurial 转 换 到 Git 是 相 当 直 接 的, 使 用 一 个 叫 作 hg-fast-export 的 工 具, 需 要 从 这 里 拷 贝 一 份 : $ git clone /tmp/fast-export 转 换 的 第 一 步 就 是 要 先 得 到 想 要 转 换 的 Mercurial 仓 库 的 完 整 克 隆 : $ hg clone <remote repo URL> /tmp/hg-repo 下 一 步 就 是 创 建 一 个 作 者 映 射 文 件 Mercurial 对 放 入 到 变 更 集 作 者 字 段 的 内 容 比 Git 更 宽 容 一 些, 所 以 这 是 一

408 个 清 理 的 好 机 会 只 需 要 用 到 bash 终 端 下 的 一 行 命 令 : $ cd /tmp/hg-repo $ hg log grep user: sort uniq sed 's/user: *//' >../authors 这 会 花 费 几 秒 钟, 具 体 要 看 项 目 提 交 历 史 有 多 少, 最 终 /tmp/authors 文 件 看 起 来 会 像 这 样 : bob bob@localhost bob <bob@company.com> bob jones <bob <AT> company <DOT> com> Bob Jones <bob@company.com> Joe Smith <joe@company.com> 在 这 个 例 子 中, 同 一 个 人 (Bob) 使 用 不 同 的 名 字 创 建 变 更 集, 其 中 一 个 实 际 上 是 正 确 的, 另 一 个 完 全 不 符 合 Git 提 交 的 规 范 Hg-fast-export 通 过 向 我 们 想 要 修 改 的 行 尾 添 加 ={new name and address} 来 修 正 这 个 问 题, 移 除 任 何 我 们 想 要 保 留 的 用 户 名 所 在 的 行 如 果 所 有 的 用 户 名 看 起 来 都 是 正 确 的, 那 我 们 根 本 就 不 需 要 这 个 文 件 在 本 例 中, 我 们 会 使 文 件 看 起 来 像 这 样 : bob=bob Jones <bob@company.com> bob@localhost=bob Jones <bob@company.com> bob jones <bob <AT> company <DOT> com>=bob Jones <bob@company.com> bob <bob@company.com>=bob Jones <bob@company.com> 下 一 步 是 创 建 一 个 新 的 Git 仓 库, 然 后 运 行 导 出 脚 本 : $ git init /tmp/converted $ cd /tmp/converted $ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors -r 选 项 告 诉 hg-fast-export 去 哪 里 寻 找 我 们 想 要 转 换 的 Mercurial 仓 库,-A 标 记 告 诉 它 在 哪 找 到 作 者 映 射 文 件 这 个 脚 本 会 分 析 Mercurial 变 更 集 然 后 将 它 们 转 换 成 Git fast-import 功 能 ( 我 们 将 在 之 后 详 细 讨 论 ) 需 要 的 脚 本 这 会 花 一 点 时 间 ( 尽 管 它 比 通 过 网 格 更 快 ), 输 出 相 当 的 冗 长 : $ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors Loaded 4 authors master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files master: Exporting simple delta revision 3/22208 with 0/1/0

409 added/changed/removed files [ ] master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files Exporting tag [0.4c] at [hg r9] [git :10] Exporting tag [0.4d] at [hg r16] [git :17] [ ] Exporting tag [3.1-rc] at [hg r21926] [git :21927] Exporting tag [3.1] at [hg r21973] [git :21974] Issued commands git-fast-import statistics: Alloc'd objects: Total objects: ( duplicates ) blobs : ( duplicates deltas of attempts) trees : ( 2851 duplicates deltas of attempts) commits: ( 0 duplicates 0 deltas of 0 attempts) tags : 0 ( 0 duplicates 0 deltas of 0 attempts) Total branches: 109 ( 2 loads ) marks: ( unique ) atoms: 1952 Memory total: 7860 KiB pools: 2235 KiB objects: 5625 KiB pack_report: getpagesize() = 4096 pack_report: core.packedgitwindowsize = pack_report: core.packedgitlimit = pack_report: pack_used_ctr = pack_report: pack_mmap_calls = pack_report: pack_open_windows = 1 / 1 pack_report: pack_mapped = / $ git shortlog -sn 369 Bob Jones 365 Joe Smith 那 看 起 来 非 常 好 所 有 Mercurial 标 签 都 已 被 转 换 成 Git 标 签,Mercurial 分 支 与 书 签 都 被 转 换 成 Git 分 支 现 在 已 经 准 备 好 将 仓 库 推 送 到 新 的 服 务 器 那 边 :

410 $ git remote add origin $ git push origin --all Perforce 下 一 个 将 要 看 到 导 入 的 系 统 是 Perforce 就 像 我 们 之 前 讨 论 过 的, 有 两 种 方 式 让 Git 与 Perforce 互 相 通 信 :gitp4 与 Perforce Git Fusion Perforce Git Fusion Git Fusion 使 这 个 过 程 毫 无 痛 苦 只 需 要 使 用 在 Git Fusion 中 讨 论 过 的 配 置 文 件 来 配 置 你 的 项 目 设 置 用 户 映 射 与 分 支, 然 后 克 隆 整 个 仓 库 Git Fusion 让 你 处 在 一 个 看 起 来 像 是 原 生 Git 仓 库 的 环 境 中, 如 果 愿 意 的 话 你 可 以 随 时 将 它 推 送 到 一 个 原 生 Git 托 管 中 如 果 你 喜 欢 的 话 甚 至 可 以 使 用 Perforce 作 为 你 的 Git 托 管 Git-p4 Git-p4 也 可 以 作 为 一 个 导 入 工 具 作 为 例 子, 我 们 将 从 Perforce 公 开 仓 库 中 导 入 Jam 项 目 为 了 设 置 客 户 端, 必 须 导 出 P4PORT 环 境 变 量 指 向 Perforce 仓 库 : $ export P4PORT=public.perforce.com:1666 NOTE 为 了 继 续 后 续 步 骤, 需 要 连 接 到 Perforce 仓 库 在 我 们 的 例 子 中 将 会 使 用 在 public.perforce.com 的 公 开 仓 库, 但 是 你 可 以 使 用 任 何 你 有 权 限 的 仓 库 运 行 git p4 clone 命 令 从 Perforce 服 务 器 导 入 Jam 项 目, 提 供 仓 库 项 目 路 径 与 你 想 要 存 放 导 入 项 目 的 路 径 : $ git-p4 clone //guest/perforce_software/jam@all p4import Importing from //guest/perforce_software/jam@all into p4import Initialized empty Git repository in /private/tmp/p4import/.git/ Import destination: refs/remotes/p4/master Importing revision 9957 (100%) 这 个 特 定 的 项 目 只 有 一 个 分 支, 但 是 如 果 你 在 分 支 视 图 ( 或 者 说 一 些 目 录 ) 中 配 置 了 一 些 分 支, 你 可 以 将 --detect-branches 选 项 传 递 给 git p4 clone 来 导 入 项 目 的 所 有 分 支 查 看 分 支 来 了 解 关 于 这 点 的 更 多 信 息 此 时 你 几 乎 已 经 完 成 了 如 果 进 入 p4import 目 录 中 并 运 行 git log, 可 以 看 到 你 的 导 入 工 作 :

411 $ git log -2 commit e5da1c909e5db f6379f2c73710c4e6 Author: giles Date: Wed Feb 8 03:13: Correction to line 355; change </UL> to </OL>. [git-p4: depot-paths = "//public/jam/src/": change = 8068] commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98 Author: kwirth <kwirth@perforce.com> Date: Tue Jul 7 01:35: Fix spelling error on Jam doc page (cummulative -> cumulative). [git-p4: depot-paths = "//public/jam/src/": change = 7304] 你 可 以 看 到 git-p4` 在 每 一 个 提 交 里 都 留 下 了 一 个 标 识 符 如 果 之 后 想 要 引 用 Perforce 的 修 改 序 号 的 话, 标 识 符 保 留 在 那 里 也 是 可 以 的 然 而, 如 果 想 要 移 除 标 识 符, 现 在 正 是 这 么 做 的 时 候 - 在 你 开 始 在 新 仓 库 中 工 作 之 前 (((git commands, filter-branch))) 可 以 使 用 `git filter-branch 将 全 部 标 识 符 移 除 $ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"' Rewrite e5da1c909e5db f6379f2c73710c4e6 (125/125) Ref 'refs/heads/master' was rewritten 如 果 运 行 git log, 你 会 看 到 所 有 提 交 的 SHA-1 校 验 和 都 改 变 了, 但 是 提 交 信 息 中 不 再 有 git-p4 字 符 串 了 : $ git log -2 commit b ed838d97f7800a54a6f9b b7 Author: giles <giles@giles@perforce.com> Date: Wed Feb 8 03:13: Correction to line 355; change </UL> to </OL>. commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff Author: kwirth <kwirth@perforce.com> Date: Tue Jul 7 01:35: Fix spelling error on Jam doc page (cummulative -> cumulative). 现 在 导 入 已 经 准 备 好 推 送 到 你 的 新 Git 服 务 器 上 了

412 TFS 如 果 你 的 团 队 正 在 将 他 们 的 源 代 码 管 理 从 TFVC 转 换 为 Git, 你 们 会 想 要 最 高 程 度 的 无 损 转 换 这 意 味 着, 虽 然 我 们 在 之 前 的 交 互 章 节 介 绍 了 git-tfs 与 git-tf 两 种 工 具, 但 是 我 们 在 本 部 分 只 能 介 绍 git-tfs, 因 为 git-tfs 支 持 分 支, 而 使 用 git-tf 代 价 太 大 NOTE 这 是 一 个 单 向 转 换 这 意 味 着 Git 仓 库 无 法 连 接 到 原 始 的 TFVC 项 目 第 一 件 事 是 映 射 用 户 名 TFVC 对 待 变 更 集 作 者 字 段 的 内 容 相 当 宽 容, 但 是 Git 需 要 人 类 可 读 的 名 字 与 邮 箱 地 址 可 以 通 过 tf 命 令 行 客 户 端 来 获 取 这 个 信 息, 像 这 样 : PS> tf history $/myproject -recursive > AUTHORS_TMP 这 会 将 历 史 中 的 所 有 变 更 集 抓 取 下 来 并 放 到 AUTHORS_TMP 文 件 中, 然 后 我 们 将 会 将 User 列 ( 第 二 个 ) 取 出 来 打 开 文 件 找 到 列 开 始 与 结 束 的 字 符 并 替 换, 在 下 面 的 命 令 行 中,cut 命 令 的 参 数 就 是 我 们 找 到 的 : PS> cat AUTHORS_TMP cut -b tail -n+3 uniq sort > AUTHORS cut 命 令 只 会 保 留 每 行 中 第 11 个 到 第 22 个 字 符 tail 命 令 会 跳 过 前 两 行, 就 是 字 段 表 头 与 ASCII 风 格 的 下 划 线 所 有 这 些 的 结 果 通 过 管 道 送 到 uniq 来 去 除 重 复, 然 后 保 存 到 AUTOHRS 文 件 中 下 一 步 是 手 动 的 ; 为 了 让 git-tfs 有 效 地 使 用 这 个 文 件, 每 一 行 必 须 是 这 种 格 式 : DOMAIN\username = User Name < @address.com> 左 边 的 部 分 是 TFVC 中 的 User 字 段, 等 号 右 边 的 部 分 是 将 被 用 作 Git 提 交 的 用 户 名 一 旦 有 了 这 个 文 件, 下 一 件 事 就 是 生 成 一 个 你 需 要 的 TFVC 项 目 的 完 整 克 隆 : PS> git tfs clone --with-branches --authors=authors $/project/trunk project_git 接 下 来 要 从 提 交 信 息 底 部 清 理 git-tfs-id 区 块 下 面 的 命 令 会 完 成 这 个 任 务 : PS> git filter-branch -f --msg-filter 'sed "s/^git-tfs-id:.*$//g"' -- --all 那 会 使 用 Git 终 端 环 境 中 的 sed 命 令 来 将 所 有 以 git-tfs-id: 开 头 的 行 替 换 为 Git 会 忽 略 的 空 白

413 全 部 完 成 后, 你 就 已 经 准 备 好 去 增 加 一 个 新 的 远 程 仓 库, 推 送 你 所 有 的 分 支 上 去, 然 后 你 的 团 队 就 可 以 开 始 用 Git 工 作 了 一 个 自 定 义 的 导 入 器 如 果 你 的 系 统 不 是 上 述 中 的 任 何 一 个, 你 需 要 在 线 查 找 一 个 导 入 器 - 针 对 许 多 其 他 系 统 有 很 多 高 质 量 的 导 入 器, 包 括 CVS Clear Case Visual Source Safe, 甚 至 是 一 个 档 案 目 录 如 果 没 有 一 个 工 具 适 合 你, 需 要 一 个 不 知 名 的 工 具, 或 者 需 要 更 大 自 由 度 的 自 定 义 导 入 过 程, 应 当 使 用 git fast-import 这 个 命 令 从 标 准 输 入 中 读 取 简 单 指 令 来 写 入 特 定 的 Git 数 据 通 过 这 种 方 式 创 建 Git 对 象 比 运 行 原 始 Git 命 令 或 直 接 写 入 原 始 对 象 ( 查 看 Git 内 部 原 理 了 解 更 多 内 容 ) 更 容 易 些 通 过 这 种 方 式 你 可 以 编 写 导 入 脚 本, 从 你 要 导 入 的 系 统 中 读 取 必 要 数 据, 然 后 直 接 打 印 指 令 到 标 准 输 出 然 后 可 以 运 行 这 个 程 序 并 通 过 git fast-import 重 定 向 管 道 输 出 为 了 快 速 演 示, 我 们 会 写 一 个 简 单 的 导 入 器 假 设 你 在 current 工 作, 有 时 候 会 备 份 你 的 项 目 到 时 间 标 签 back_yyyy_mm_dd 备 份 目 录 中, 你 想 要 将 这 些 导 入 到 Git 中 目 录 结 构 看 起 来 是 这 样 : $ ls /opt/import_from back_2014_01_02 back_2014_01_04 back_2014_01_14 back_2014_02_03 current 为 了 导 入 一 个 Git 目 录, 需 要 了 解 Git 如 何 存 储 它 的 数 据 你 可 能 记 得,Git 在 底 层 存 储 指 向 内 容 快 照 的 提 交 对 象 的 链 表 所 有 要 做 的 就 是 告 诉 fast-import 哪 些 内 容 是 快 照, 哪 个 提 交 数 据 指 向 它 们, 以 及 它 们 进 入 的 顺 序 你 的 策 略 是 一 次 访 问 一 个 快 照, 然 后 用 每 个 目 录 中 的 内 容 创 建 提 交, 并 且 将 每 一 个 提 交 与 前 一 个 连 接 起 来 如 同 我 们 在 使 用 强 制 策 略 的 一 个 例 子 里 做 的, 我 们 将 会 使 用 Ruby 写 这 个, 因 为 它 是 我 们 平 常 工 作 中 使 用 的 并 且 它 很 容 易 读 懂 可 以 使 用 任 何 你 熟 悉 的 东 西 来 非 常 轻 松 地 写 这 个 例 子 - 它 只 需 要 将 合 适 的 信 息 打 印 到 标 准 输 出 然 而, 如 果 你 在 Windows 上, 这 意 味 着 需 要 特 别 注 意 不 要 引 入 回 车 符 到 行 尾 - git fast-import 非 常 特 别 地 只 接 受 换 行 符 (LF) 而 不 是 Windows 使 用 的 回 车 换 行 符 (CRLF) 现 在 开 始, 需 要 进 入 目 标 目 录 中 并 识 别 每 一 个 子 目 录, 每 一 个 都 是 你 要 导 入 为 提 交 的 快 照 要 进 入 到 每 个 子 目 录 中 并 为 导 出 它 打 印 必 要 的 命 令 基 本 主 循 环 像 这 个 样 子 :

414 last_mark = nil # loop through the directories Dir.chdir(ARGV[0]) do Dir.glob("*").each do dir next if File.file?(dir) # move into the target directory Dir.chdir(dir) do last_mark = print_export(dir, last_mark) end end end 在 每 个 目 录 内 运 行 print_export, 将 会 拿 到 清 单 并 标 记 之 前 的 快 照, 然 后 返 回 清 单 并 标 记 现 在 的 快 照 ; 通 过 这 种 方 式, 可 以 将 它 们 合 适 地 连 接 在 一 起 标 记 是 一 个 给 提 交 标 识 符 的 fast-import 术 语 ; 当 你 创 建 提 交, 为 每 一 个 提 交 赋 予 一 个 标 记 来 将 它 与 其 他 提 交 连 接 在 一 起 这 样, 在 你 的 print_export 方 法 中 第 一 件 要 做 的 事 就 是 从 目 录 名 字 生 成 一 个 标 记 : mark = convert_dir_to_mark(dir) 可 以 创 建 一 个 目 录 的 数 组 并 使 用 索 引 做 为 标 记, 因 为 标 记 必 须 是 一 个 整 数 方 法 类 似 这 样 : $marks = [] def convert_dir_to_mark(dir) if!$marks.include?(dir) $marks << dir end ($marks.index(dir) + 1).to_s end 既 然 有 一 个 整 数 代 表 你 的 提 交, 那 还 要 给 提 交 元 数 据 一 个 日 期 因 为 目 录 名 字 表 达 了 日 期, 所 以 你 将 会 从 中 解 析 出 日 期 你 的 print_export 文 件 的 下 一 行 是 date = convert_dir_to_date(dir) convert_dir_to_date 定 义 为

415 def convert_dir_to_date(dir) if dir == 'current' return Time.now().to_i else dir = dir.gsub('back_', '') (year, month, day) = dir.split('_') return Time.local(year, month, day).to_i end end 那 会 返 回 每 一 个 目 录 日 期 的 整 数 最 后 一 项 每 个 提 交 需 要 的 元 数 据 是 提 交 者 信 息, 它 将 会 被 硬 编 码 在 全 局 变 量 中 : $author = 'John Doe <john@example.com>' 现 在 准 备 开 始 为 你 的 导 入 器 打 印 出 提 交 数 据 初 始 信 息 声 明 定 义 了 一 个 提 交 对 象 与 它 所 在 的 分 支, 紧 接 着 一 个 你 生 成 的 标 记 提 交 者 信 息 与 提 交 信 息 然 后 是 一 个 之 前 的 提 交, 如 果 它 存 在 的 话 代 码 看 起 来 像 这 样 : # print the import information puts 'commit refs/heads/master' puts 'mark :' + mark puts "committer #{$author} #{date} -0700" export_data('imported from ' + dir) puts 'from :' + last_mark if last_mark 我 们 将 硬 编 码 时 区 信 息 (-0700), 因 为 这 样 很 容 易 如 果 从 其 他 系 统 导 入, 必 须 指 定 为 一 个 偏 移 的 时 区 提 交 信 息 必 须 指 定 为 特 殊 的 格 式 : data (size)\n(contents) 这 个 格 式 包 括 文 本 数 据 将 要 读 取 数 据 的 大 小 一 个 换 行 符 最 终 的 数 据 因 为 之 后 还 需 要 为 文 件 内 容 指 定 相 同 的 数 据 格 式, 你 需 要 创 建 一 个 帮 助 函 数,export_data: def export_data(string) print "data #{string.size}\n#{string}" end 剩 下 的 工 作 就 是 指 定 每 一 个 快 照 的 文 件 内 容 这 很 轻 松, 因 为 每 一 个 目 录 都 是 一 个 快 照 - 可 以 在 目 录 中 的 每 一 个 文 件 内 容 后 打 印 deleteall 命 令 Git 将 会 适 当 地 记 录 每 一 个 快 照 :

416 puts 'deleteall' Dir.glob("**/*").each do file next if!file.file?(file) inline_data(file) end 注 意 : 因 为 大 多 数 系 统 认 为 他 们 的 版 本 是 从 一 个 提 交 变 化 到 另 一 个 提 交,fast-import 也 可 以 为 每 一 个 提 交 执 行 命 令 来 指 定 哪 些 文 件 是 添 加 的 删 除 的 或 修 改 的 与 新 内 容 是 哪 些 可 以 计 算 快 照 间 的 不 同 并 只 提 供 这 些 数 据, 但 是 这 样 做 会 很 复 杂 - 也 可 以 把 所 有 数 据 给 Git 然 后 让 它 为 你 指 出 来 如 果 这 更 适 合 你 的 数 据, 查 阅 fastimport man 帮 助 页 来 了 解 如 何 以 这 种 方 式 提 供 你 的 数 据 这 种 列 出 新 文 件 内 容 或 用 新 内 容 指 定 修 改 文 件 的 格 式 如 同 下 面 的 内 容 : M 644 inline path/to/file data (size) (file contents) 这 里,644 是 模 式 ( 如 果 你 有 可 执 行 文 件, 反 而 你 需 要 检 测 并 指 定 755),inline 表 示 将 会 立 即 把 内 容 放 在 本 行 之 后 你 的 inline_data 方 法 看 起 来 像 这 样 : def inline_data(file, code = 'M', mode = '644') content = File.read(file) puts "#{code} #{mode} inline #{file}" export_data(content) end 可 以 重 用 之 前 定 义 的 export_data 方 法, 因 为 它 与 你 定 义 的 提 交 信 息 数 据 的 方 法 一 样 最 后 一 件 你 需 要 做 的 是 返 回 当 前 的 标 记 以 便 它 可 以 传 给 下 一 个 迭 代 : return mark NOTE 如 果 在 Windows 上 还 需 要 确 保 增 加 一 个 额 外 步 骤 正 如 之 前 提 到 的,Windows 使 用 CRLF 作 为 换 行 符 而 git fast-import 只 接 受 LF 为 了 修 正 这 个 问 题 使 git fast-import 正 常 工 作, 你 需 要 告 诉 ruby 使 用 LF 代 替 CRLF: $stdout.binmode 就 是 这 样 这 是 全 部 的 脚 本 :

417 #!/usr/bin/env ruby $stdout.binmode $author = "John Doe <john@example.com>" $marks = [] def convert_dir_to_mark(dir) if!$marks.include?(dir) $marks << dir end ($marks.index(dir)+1).to_s end def convert_dir_to_date(dir) if dir == 'current' return Time.now().to_i else dir = dir.gsub('back_', '') (year, month, day) = dir.split('_') return Time.local(year, month, day).to_i end end def export_data(string) print "data #{string.size}\n#{string}" end def inline_data(file, code='m', mode='644') content = File.read(file) puts "#{code} #{mode} inline #{file}" export_data(content) end def print_export(dir, last_mark) date = convert_dir_to_date(dir) mark = convert_dir_to_mark(dir) puts 'commit refs/heads/master' puts "mark :#{mark}" puts "committer #{$author} #{date} -0700" export_data("imported from #{dir}") puts "from :#{last_mark}" if last_mark puts 'deleteall' Dir.glob("**/*").each do file next if!file.file?(file) inline_data(file) end

418 end mark # Loop through the directories last_mark = nil Dir.chdir(ARGV[0]) do Dir.glob("*").each do dir next if File.file?(dir) end end # move into the target directory Dir.chdir(dir) do last_mark = print_export(dir, last_mark) end 如 果 运 行 这 个 脚 本, 你 会 得 到 类 似 下 面 的 内 容 : $ ruby import.rb /opt/import_from commit refs/heads/master mark :1 committer John Doe <john@example.com> data 29 imported from back_2014_01_02deleteall M 644 inline README.md data 28 # Hello This is my readme. commit refs/heads/master mark :2 committer John Doe <john@example.com> data 29 imported from back_2014_01_04from :1 deleteall M 644 inline main.rb data 34 #!/bin/env ruby puts "Hey there" M 644 inline README.md (...) 为 了 运 行 导 入 器, 将 这 些 输 出 用 管 道 重 定 向 到 你 想 要 导 入 的 Git 目 录 中 的 git fast-import 可 以 创 建 一 个 新 的 目 录 并 在 其 中 运 行 git init 作 为 开 始, 然 后 运 行 你 的 脚 本 :

419 $ git init Initialized empty Git repository in /opt/import_to/.git/ $ ruby import.rb /opt/import_from git fast-import git-fast-import statistics: Alloc'd objects: 5000 Total objects: 13 ( 6 duplicates ) blobs : 5 ( 4 duplicates 3 deltas of 5 attempts) trees : 4 ( 1 duplicates 0 deltas of 4 attempts) commits: 4 ( 1 duplicates 0 deltas of 0 attempts) tags : 0 ( 0 duplicates 0 deltas of 0 attempts) Total branches: 1 ( 1 loads ) marks: 1024 ( 5 unique ) atoms: 2 Memory total: 2344 KiB pools: 2110 KiB objects: 234 KiB pack_report: getpagesize() = 4096 pack_report: core.packedgitwindowsize = pack_report: core.packedgitlimit = pack_report: pack_used_ctr = 10 pack_report: pack_mmap_calls = 5 pack_report: pack_open_windows = 2 / 2 pack_report: pack_mapped = 1457 / 正 如 你 所 看 到 的, 当 它 成 功 完 成 时, 它 会 给 你 一 串 关 于 它 完 成 内 容 的 统 计 这 本 例 中, 一 共 导 入 了 13 个 对 象 4 次 提 交 到 1 个 分 支 现 在, 可 以 运 行 git log 来 看 一 下 你 的 新 历 史 : $ git log -2 commit 3caa046d4aac682a ccdfbe0d3fdee498 Author: John Doe <john@example.com> Date: Tue Jul 29 19:39: imported from current commit 4afc2b945d0d3c8cd00556fbe2e dc9def Author: John Doe <john@example.com> Date: Mon Feb 3 01:00: imported from back_2014_02_03

420 做 得 很 好 - 一 个 漂 亮 干 净 的 Git 仓 库 要 注 意 的 一 点 是 并 没 有 检 出 任 何 东 西 - 一 开 始 你 的 工 作 目 录 内 并 没 有 任 何 文 件 为 了 得 到 他 们, 你 必 须 将 分 支 重 置 到 master 所 在 的 地 方 : $ ls $ git reset --hard master HEAD is now at 3caa046 imported from current $ ls README.md main.rb 可 以 通 过 fast-import 工 具 做 很 多 事 情 - 处 理 不 同 模 式 二 进 制 数 据 多 个 分 支 与 合 并 标 签 进 度 指 示 等 等 一 些 更 复 杂 情 形 下 的 例 子 可 以 在 Git 源 代 码 目 录 中 的 contrib/fast-import 目 录 中 找 到 总 结 你 会 觉 得 将 Git 作 为 其 他 版 本 控 制 系 统 的 客 户 端, 或 者 在 数 据 无 损 的 情 况 下 将 几 乎 任 何 一 个 现 有 的 仓 库 导 入 到 Git, 都 是 一 件 很 惬 意 的 事 在 下 一 章, 我 们 将 要 讲 解 Git 的 原 始 内 部 数 据, 如 果 需 要 的 话 你 就 可 以 加 工 每 一 个 字 节

421 Git 内 部 原 理 无 论 是 从 之 前 的 章 节 直 接 跳 到 本 章, 还 是 读 完 了 其 余 章 节 一 直 到 这 你 都 将 在 本 章 见 识 到 Git 的 内 部 工 作 原 理 和 实 现 方 式 我 们 发 现 学 习 这 部 分 内 容 对 于 理 解 Git 的 用 途 和 强 大 至 关 重 要 不 过 也 有 人 认 为 这 些 内 容 对 于 初 学 者 而 言 可 能 难 以 理 解 且 过 于 复 杂 因 此 我 们 把 这 部 分 内 容 放 在 最 后 一 章, 在 学 习 过 程 中 可 以 先 阅 读 这 部 分, 也 可 以 晚 点 阅 读 这 部 分, 这 取 决 于 你 自 己 无 论 如 何, 既 然 已 经 读 到 了 这 里, 就 让 我 们 开 始 吧 首 先 要 弄 明 白 一 点, 从 根 本 上 来 讲 Git 是 一 个 内 容 寻 址 (content-addressable) 文 件 系 统, 并 在 此 之 上 提 供 了 一 个 版 本 控 制 系 统 的 用 户 界 面 马 上 你 就 会 学 到 这 意 味 着 什 么 早 期 的 Git( 主 要 是 1.5 之 前 的 版 本 ) 的 用 户 界 面 要 比 现 在 复 杂 的 多, 因 为 它 更 侧 重 于 作 为 一 个 文 件 系 统, 而 不 是 一 个 打 磨 过 的 版 本 控 制 系 统 不 时 会 有 一 些 陈 词 滥 调 抱 怨 早 期 那 个 晦 涩 复 杂 的 Git 用 户 界 面 ; 不 过 最 近 几 年 来, 它 已 经 被 改 进 到 不 输 于 任 何 其 他 版 本 控 制 系 统 地 清 晰 易 用 了 内 容 寻 址 文 件 系 统 层 是 一 套 相 当 酷 的 东 西, 所 以 在 本 章 我 们 会 先 讲 解 这 部 分 内 容 随 后 我 们 会 学 习 传 输 机 制 和 版 本 库 管 理 任 务 你 迟 早 会 和 它 们 打 交 道 底 层 命 令 和 高 层 命 令 本 书 旨 在 讨 论 如 何 通 过 checkout branch remote 等 大 约 30 个 诸 如 此 类 动 词 形 式 的 命 令 来 玩 转 Git 然 而, 由 于 Git 最 初 是 一 套 面 向 版 本 控 制 系 统 的 工 具 集, 而 不 是 一 个 完 整 的 用 户 友 好 的 版 本 控 制 系 统, 所 以 它 还 包 含 了 一 部 分 用 于 完 成 底 层 工 作 的 命 令 这 些 命 令 被 设 计 成 能 以 UNIX 命 令 行 的 风 格 连 接 在 一 起, 抑 或 藉 由 脚 本 调 用, 来 完 成 工 作 这 部 分 命 令 一 般 被 称 作 底 层 (plumbing) 命 令, 而 那 些 更 友 好 的 命 令 则 被 称 作 高 层 (porcelain) 命 令 本 书 前 九 章 专 注 于 探 讨 高 层 命 令 然 而 在 本 章, 我 们 将 主 要 面 对 底 层 命 令 因 为, 底 层 命 令 得 以 让 你 窥 探 Git 内 部 的 工 作 机 制, 也 有 助 于 说 明 Git 是 如 何 完 成 工 作 的, 以 及 它 为 何 如 此 运 作 多 数 底 层 命 令 并 不 面 向 最 终 用 户 : 它 们 更 适 合 作 为 新 命 令 和 自 定 义 脚 本 的 组 成 部 分 当 在 一 个 新 目 录 或 已 有 目 录 执 行 git init 时,Git 会 创 建 一 个.git 目 录 这 个 目 录 包 含 了 几 乎 所 有 Git 存 储 和 操 作 的 对 象 如 若 想 备 份 或 复 制 一 个 版 本 库, 只 需 把 这 个 目 录 拷 贝 至 另 一 处 即 可 本 章 探 讨 的 所 有 内 容, 均 位 于 这 个 目 录 内 该 目 录 的 结 构 如 下 所 示 : $ ls -F1 HEAD config* description hooks/ info/ objects/ refs/ 该 目 录 下 可 能 还 会 包 含 其 他 文 件, 不 过 对 于 一 个 全 新 的 git init 版 本 库, 这 将 是 你 看 到 的 默 认 结 构 description 文 件 仅 供 GitWeb 程 序 使 用, 我 们 无 需 关 心 config 文 件 包 含 项 目 特 有 的 配 置 选 项 info

422 目 录 包 含 一 个 全 局 性 排 除 (global exclude) 文 件, 用 以 放 置 那 些 不 希 望 被 记 录 在.gitignore 文 件 中 的 忽 略 模 式 (ignored patterns) hooks 目 录 包 含 客 户 端 或 服 务 端 的 钩 子 脚 本 (hook scripts), 在 Git 钩 子 中 这 部 分 话 题 已 被 详 细 探 讨 过 剩 下 的 四 个 条 目 很 重 要 :HEAD 文 件 ( 尚 待 创 建 的 )index 文 件, 和 objects 目 录 refs 目 录 这 些 条 目 是 Git 的 核 心 组 成 部 分 objects 目 录 存 储 所 有 数 据 内 容 ;refs 目 录 存 储 指 向 数 据 ( 分 支 ) 的 提 交 对 象 的 指 针 ;HEAD 文 件 指 示 目 前 被 检 出 的 分 支 ;index 文 件 保 存 暂 存 区 信 息 我 们 将 详 细 地 逐 一 检 视 这 四 部 分, 以 期 理 解 Git 是 如 何 运 转 的 Git 对 象 Git 是 一 个 内 容 寻 址 文 件 系 统 看 起 来 很 酷, 但 这 是 什 么 意 思 呢? 这 意 味 着,Git 的 核 心 部 分 是 一 个 简 单 的 键 值 对 数 据 库 (key-value data store) 你 可 以 向 该 数 据 库 插 入 任 意 类 型 的 内 容, 它 会 返 回 一 个 键 值, 通 过 该 键 值 可 以 在 任 意 时 刻 再 次 检 索 (retrieve) 该 内 容 可 以 通 过 底 层 命 令 hash-object 来 演 示 上 述 效 果 该 命 令 可 将 任 意 数 据 保 存 于.git 目 录, 并 返 回 相 应 的 键 值 首 先, 我 们 需 要 初 始 化 一 个 新 的 Git 版 本 库, 并 确 认 objects 目 录 为 空 : $ git init test Initialized empty Git repository in /tmp/test/.git/ $ cd test $ find.git/objects.git/objects.git/objects/info.git/objects/pack $ find.git/objects -type f 可 以 看 到 Git 对 objects 目 录 进 行 了 初 始 化, 并 创 建 了 pack 和 info 子 目 录, 但 均 为 空 接 着, 往 Git 数 据 库 存 入 一 些 文 本 : $ echo 'test content' git hash-object -w --stdin d670460b4b4aece5915caf5c68d12f560a9fe3e4 -w 选 项 指 示 hash-object 命 令 存 储 数 据 对 象 ; 若 不 指 定 此 选 项, 则 该 命 令 仅 返 回 对 应 的 键 值 --stdin 选 项 则 指 示 该 命 令 从 标 准 输 入 读 取 内 容 ; 若 不 指 定 此 选 项, 则 须 在 命 令 尾 部 给 出 待 存 储 文 件 的 路 径 该 命 令 输 出 一 个 长 度 为 40 个 字 符 的 校 验 和 这 是 一 个 SHA-1 哈 希 值 一 个 将 待 存 储 的 数 据 外 加 一 个 头 部 信 息 (header) 一 起 做 SHA-1 校 验 运 算 而 得 的 校 验 和 后 文 会 简 要 讨 论 该 头 部 信 息 现 在 我 们 可 以 查 看 Git 是 如 何 存 储 数 据 的 : $ find.git/objects -type f.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 可 以 在 objects 目 录 下 看 到 一 个 文 件 这 就 是 开 始 时 Git 存 储 内 容 的 方 式 一 个 文 件 对 应 一 条 内 容, 以 该 内 容 加 上 特 定 头 部 信 息 一 起 的 SHA-1 校 验 和 为 文 件 命 名 校 验 和 的 前 两 个 字 符 用 于 命 名 子 目 录, 余 下 的 38 个 字 符

423 则 用 作 文 件 名 可 以 通 过 cat-file 命 令 从 Git 那 里 取 回 数 据 这 个 命 令 简 直 就 是 一 把 剖 析 Git 对 象 的 瑞 士 军 刀 为 cat-file 指 定 -p 选 项 可 指 示 该 命 令 自 动 判 断 内 容 的 类 型, 并 为 我 们 显 示 格 式 友 好 的 内 容 : $ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4 test content 至 此, 你 已 经 掌 握 了 如 何 向 Git 中 存 入 内 容, 以 及 如 何 将 它 们 取 出 我 们 同 样 可 以 将 这 些 操 作 应 用 于 文 件 中 的 内 容 例 如, 可 以 对 一 个 文 件 进 行 简 单 的 版 本 控 制 首 先, 创 建 一 个 新 文 件 并 将 其 内 容 存 入 数 据 库 : $ echo 'version 1' > test.txt $ git hash-object -w test.txt 83baae61804e65cc73a7201a c76066a30 接 着, 向 文 件 里 写 入 新 内 容, 并 再 次 将 其 存 入 数 据 库 : $ echo 'version 2' > test.txt $ git hash-object -w test.txt 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a 数 据 库 记 录 下 了 该 文 件 的 两 个 不 同 版 本, 当 然 之 前 我 们 存 入 的 第 一 条 内 容 也 还 在 : $ find.git/objects -type f.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a.git/objects/83/baae61804e65cc73a7201a c76066a30.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 现 在 可 以 把 文 件 内 容 恢 复 到 第 一 个 版 本 : $ git cat-file -p 83baae61804e65cc73a7201a c76066a30 > test.txt $ cat test.txt version 1 或 者 第 二 个 版 本 :

424 $ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt $ cat test.txt version 2 然 而, 记 住 文 件 的 每 一 个 版 本 所 对 应 的 SHA-1 值 并 不 现 实 ; 另 一 个 问 题 是, 在 这 个 ( 简 单 的 版 本 控 制 ) 系 统 中, 文 件 名 并 没 有 被 保 存 我 们 仅 保 存 了 文 件 的 内 容 上 述 类 型 的 对 象 我 们 称 之 为 数 据 对 象 (blob object) 利 用 cat-file -t 命 令, 可 以 让 Git 告 诉 我 们 其 内 部 存 储 的 任 何 对 象 类 型, 只 要 给 定 该 对 象 的 SHA-1 值 : $ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 树 对 象 接 下 来 要 探 讨 的 对 象 类 型 是 树 对 象 (tree object), 它 能 解 决 文 件 名 保 存 的 问 题, 也 允 许 我 们 将 多 个 文 件 组 织 到 一 起 Git 以 一 种 类 似 于 UNIX 文 件 系 统 的 方 式 存 储 内 容, 但 作 了 些 许 简 化 所 有 内 容 均 以 树 对 象 和 数 据 对 象 的 形 式 存 储, 其 中 树 对 象 对 应 了 UNIX 中 的 目 录 项, 数 据 对 象 则 大 致 上 对 应 了 inodes 或 文 件 内 容 一 个 树 对 象 包 含 了 一 条 或 多 条 树 对 象 记 录 (tree entry), 每 条 记 录 含 有 一 个 指 向 数 据 对 象 或 者 子 树 对 象 的 SHA-1 指 针, 以 及 相 应 的 模 式 类 型 文 件 名 信 息 例 如, 某 项 目 当 前 对 应 的 最 新 树 对 象 可 能 是 这 样 的 : $ git cat-file -p master^{tree} blob a906cb2a4a904a152e80877d daad0c859 README blob 8f f9404f26296befa88755fc2598c289 Rakefile tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib master^{tree} 语 法 表 示 master 分 支 上 最 新 的 提 交 所 指 向 的 树 对 象 请 注 意,lib 子 目 录 ( 所 对 应 的 那 条 树 对 象 记 录 ) 并 不 是 一 个 数 据 对 象, 而 是 一 个 指 针, 其 指 向 的 是 另 一 个 树 对 象 : $ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb 从 概 念 上 讲,Git 内 部 存 储 的 数 据 有 点 像 这 样 :

425 Figure 149. 简 化 版 的 Git 数 据 模 型 你 可 以 轻 松 创 建 自 己 的 树 对 象 通 常,Git 根 据 某 一 时 刻 暂 存 区 ( 即 index 区 域, 下 同 ) 所 表 示 的 状 态 创 建 并 记 录 一 个 对 应 的 树 对 象, 如 此 重 复 便 可 依 次 记 录 ( 某 个 时 间 段 内 ) 一 系 列 的 树 对 象 因 此, 为 创 建 一 个 树 对 象, 首 先 需 要 通 过 暂 存 一 些 文 件 来 创 建 一 个 暂 存 区 可 以 通 过 底 层 命 令 update-index 为 一 个 单 独 文 件 我 们 的 test.txt 文 件 的 首 个 版 本 创 建 一 个 暂 存 区 利 用 该 命 令, 可 以 把 test.txt 文 件 的 首 个 版 本 人 为 地 加 入 一 个 新 的 暂 存 区 必 须 为 上 述 命 令 指 定 --add 选 项, 因 为 此 前 该 文 件 并 不 在 暂 存 区 中 ( 我 们 甚 至 都 还 没 来 得 及 创 建 一 个 暂 存 区 呢 ); 同 样 必 需 的 还 有 --cacheinfo 选 项, 因 为 将 要 添 加 的 文 件 位 于 Git 数 据 库 中, 而 不 是 位 于 当 前 目 录 下 同 时, 需 要 指 定 文 件 模 式 SHA-1 与 文 件 名 : $ git update-index --add --cacheinfo \ 83baae61804e65cc73a7201a c76066a30 test.txt 本 例 中, 我 们 指 定 的 文 件 模 式 为 , 表 明 这 是 一 个 普 通 文 件 其 他 选 择 包 括 :100755, 表 示 一 个 可 执 行 文 件 ;120000, 表 示 一 个 符 号 链 接 这 里 的 文 件 模 式 参 考 了 常 见 的 UNIX 文 件 模 式, 但 远 没 那 么 灵 活 上 述 三 种 模 式 即 是 Git 文 件 ( 即 数 据 对 象 ) 的 所 有 合 法 模 式 ( 当 然, 还 有 其 他 一 些 模 式, 但 用 于 目 录 项 和 子 模 块 ) 现 在, 可 以 通 过 write-tree 命 令 将 暂 存 区 内 容 写 入 一 个 树 对 象 此 处 无 需 指 定 -w 选 项 如 果 某 个 树 对 象 此 前 并 不 存 在 的 话, 当 调 用 write-tree 命 令 时, 它 会 根 据 当 前 暂 存 区 状 态 自 动 创 建 一 个 新 的 树 对 象 :

426 $ git write-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f blob 83baae61804e65cc73a7201a c76066a30 test.txt 不 妨 验 证 一 下 它 确 实 是 一 个 树 对 象 : $ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree 接 着 我 们 来 创 建 一 个 新 的 树 对 象, 它 包 括 test.txt 文 件 的 第 二 个 版 本, 以 及 一 个 新 的 文 件 : $ echo 'new file' > new.txt $ git update-index test.txt $ git update-index --add new.txt 暂 存 区 现 在 包 含 了 test.txt 文 件 的 新 版 本, 和 一 个 新 文 件 :new.txt 记 录 下 这 个 目 录 树 ( 将 当 前 暂 存 区 的 状 态 记 录 为 一 个 树 对 象 ), 然 后 观 察 它 的 结 构 : $ git write-tree 0155eb a0f03eb265b69f5a2d56f341 $ git cat-file -p 0155eb a0f03eb265b69f5a2d56f blob fa49b ad f2a75f74e3671e92 new.txt blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt 我 们 注 意 到, 新 的 树 对 象 包 含 两 条 文 件 记 录, 同 时 test.txt 的 SHA-1 值 (1f7a7a) 是 先 前 值 的 第 二 版 只 是 为 了 好 玩 : 你 可 以 将 第 一 个 树 对 象 加 入 第 二 个 树 对 象, 使 其 成 为 新 的 树 对 象 的 一 个 子 目 录 通 过 调 用 readtree 命 令, 可 以 把 树 对 象 读 入 暂 存 区 本 例 中, 可 以 通 过 对 read-tree 指 定 --prefix 选 项, 将 一 个 已 有 的 树 对 象 作 为 子 树 读 入 暂 存 区 : $ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579 $ git write-tree 3c4e9cd789d88d8d89c c3585e41b0e614 $ git cat-file -p 3c4e9cd789d88d8d89c c3585e41b0e tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak blob fa49b ad f2a75f74e3671e92 new.txt blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt 如 果 基 于 这 个 新 的 树 对 象 创 建 一 个 工 作 目 录, 你 会 发 现 工 作 目 录 的 根 目 录 包 含 两 个 文 件 以 及 一 个 名 为 bak 的 子 目 录, 该 子 目 录 包 含 test.txt 文 件 的 第 一 个 版 本 可 以 认 为 Git 内 部 存 储 着 的 用 于 表 示 上 述 结 构 的 数 据 是 这 样

427 的 : Figure 150. 当 前 Git 的 数 据 内 容 结 构 提 交 对 象 现 在 有 三 个 树 对 象, 分 别 代 表 了 我 们 想 要 跟 踪 的 不 同 项 目 快 照 然 而 问 题 依 旧 : 若 想 重 用 这 些 快 照, 你 必 须 记 住 所 有 三 个 SHA-1 哈 希 值 并 且, 你 也 完 全 不 知 道 是 谁 保 存 了 这 些 快 照, 在 什 么 时 刻 保 存 的, 以 及 为 什 么 保 存 这 些 快 照 而 以 上 这 些, 正 是 提 交 对 象 (commit object) 能 为 你 保 存 的 基 本 信 息 可 以 通 过 调 用 commit-tree 命 令 创 建 一 个 提 交 对 象, 为 此 需 要 指 定 一 个 树 对 象 的 SHA-1 值, 以 及 该 提 交 的 父 提 交 对 象 ( 如 果 有 的 话 ) 我 们 从 之 前 创 建 的 第 一 个 树 对 象 开 始 : $ echo 'first commit' git commit-tree d8329f fdf4fc3344e67ab068f836878b6c4951e3b15f3d 现 在 可 以 通 过 cat-file 命 令 查 看 这 个 新 提 交 对 象 :

428 $ git cat-file -p fdf4fc3 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 author Scott Chacon committer Scott Chacon first commit 提 交 对 象 的 格 式 很 简 单 : 它 先 指 定 一 个 顶 层 树 对 象, 代 表 当 前 项 目 快 照 ; 然 后 是 作 者 / 提 交 者 信 息 ( 依 据 你 的 user.name 和 user. 配 置 来 设 定, 外 加 一 个 时 间 戳 ); 留 空 一 行, 最 后 是 提 交 注 释 接 着, 我 们 将 创 建 另 两 个 提 交 对 象, 它 们 分 别 引 用 各 自 的 上 一 个 提 交 ( 作 为 其 父 提 交 对 象 ): $ echo 'second commit' git commit-tree 0155eb -p fdf4fc3 cac0cab538b970a37ea1e769cbbde608743bc96d $ echo 'third commit' git commit-tree 3c4e9c -p cac0cab 1a410efbd13591db ebc7a059dd55cfe9 这 三 个 提 交 对 象 分 别 指 向 之 前 创 建 的 三 个 树 对 象 快 照 中 的 一 个 现 在, 如 果 对 最 后 一 个 提 交 的 SHA-1 值 运 行 git log 命 令, 会 出 乎 意 料 的 发 现, 你 已 有 一 个 货 真 价 实 的 可 由 git log 查 看 的 Git 提 交 历 史 了 :

429 $ git log --stat 1a410e commit 1a410efbd13591db ebc7a059dd55cfe9 Author: Scott Chacon Date: Fri May 22 18:15: third commit bak/test.txt file changed, 1 insertion(+) commit cac0cab538b970a37ea1e769cbbde608743bc96d Author: Scott Chacon <schacon@gmail.com> Date: Fri May 22 18:14: second commit new.txt 1 + test.txt files changed, 2 insertions(+), 1 deletion(-) commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d Author: Scott Chacon <schacon@gmail.com> Date: Fri May 22 18:09: first commit test.txt file changed, 1 insertion(+) 太 神 奇 了 : 就 在 刚 才, 你 没 有 借 助 任 何 上 层 命 令, 仅 凭 几 个 底 层 操 作 便 完 成 了 一 个 Git 提 交 历 史 的 创 建 这 就 是 每 次 我 们 运 行 git add 和 git commit 命 令 时, Git 所 做 的 实 质 工 作 将 被 改 写 的 文 件 保 存 为 数 据 对 象, 更 新 暂 存 区, 记 录 树 对 象, 最 后 创 建 一 个 指 明 了 顶 层 树 对 象 和 父 提 交 的 提 交 对 象 这 三 种 主 要 的 Git 对 象 数 据 对 象 树 对 象 提 交 对 象 最 初 均 以 单 独 文 件 的 形 式 保 存 在.git/objects 目 录 下 下 面 列 出 了 目 前 示 例 目 录 内 的 所 有 对 象, 辅 以 各 自 所 保 存 内 容 的 注 释 : $ find.git/objects -type f.git/objects/01/55eb a0f03eb265b69f5a2d56f341 # tree 2.git/objects/1a/410efbd13591db ebc7a059dd55cfe9 # commit 3.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2.git/objects/3c/4e9cd789d88d8d89c c3585e41b0e614 # tree 3.git/objects/83/baae61804e65cc73a7201a c76066a30 # test.txt v1.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1.git/objects/fa/49b ad f2a75f74e3671e92 # new.txt.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

430 如 果 跟 踪 所 有 的 内 部 指 针, 将 得 到 一 个 类 似 下 面 的 对 象 关 系 图 : Figure 151. 你 的 Git 目 录 下 的 所 有 对 象 对 象 存 储 前 文 曾 提 及, 在 存 储 内 容 时, 会 有 个 头 部 信 息 一 并 被 保 存 让 我 们 略 花 些 时 间 来 看 看 Git 是 如 何 存 储 其 对 象 的 通 过 在 Ruby 脚 本 语 言 中 交 互 式 地 演 示, 你 将 看 到 一 个 数 据 对 象 本 例 中 是 字 符 串 what is up, doc? 是 如 何 被 存 储 的 可 以 通 过 irb 命 令 启 动 Ruby 的 交 互 模 式 : $ irb >> content = "what is up, doc?" => "what is up, doc?" Git 以 对 象 类 型 作 为 开 头 来 构 造 一 个 头 部 信 息, 本 例 中 是 一 个 blob 字 符 串 接 着 Git 会 添 加 一 个 空 格, 随 后 是 数 据 内 容 的 长 度, 最 后 是 一 个 空 字 节 (null byte):

431 >> header = "blob #{content.length}\0" => "blob 16\u0000" Git 会 将 上 述 头 部 信 息 和 原 始 数 据 拼 接 起 来, 并 计 算 出 这 条 新 内 容 的 SHA-1 校 验 和 在 Ruby 中 可 以 这 样 计 算 SHA-1 值 先 通 过 require 命 令 导 入 SHA-1 digest 库, 然 后 对 目 标 字 符 串 调 用 Digest::SHA1.hexdigest(): >> store = header + content => "blob 16\u0000what is up, doc?" >> require 'digest/sha1' => true >> sha1 = Digest::SHA1.hexdigest(store) => "bd9dbf5aae1a3862dd b20206e5fc37" Git 会 通 过 zlib 压 缩 这 条 新 内 容 在 Ruby 中 可 以 借 助 zlib 库 做 到 这 一 点 先 导 入 相 应 的 库, 然 后 对 目 标 内 容 调 用 Zlib::Deflate.deflate(): >> require 'zlib' => true >> zlib_content = Zlib::Deflate.deflate(store) => "x\x9ck\xca\xc9or04c(\xcfh,q\xc8,v(-\xd0qh\xc9o\xb6\a\x00_\x1c\a\x9d" 最 后, 需 要 将 这 条 经 由 zlib 压 缩 的 内 容 写 入 磁 盘 上 的 某 个 对 象 要 先 确 定 待 写 入 对 象 的 路 径 (SHA-1 值 的 前 两 个 字 符 作 为 子 目 录 名 称, 后 38 个 字 符 则 作 为 子 目 录 内 文 件 的 名 称 ) 如 果 该 子 目 录 不 存 在, 可 以 通 过 Ruby 中 的 FileUtils.mkdir_p() 函 数 来 创 建 它 接 着, 通 过 File.open() 打 开 这 个 文 件 最 后, 对 上 一 步 中 得 到 的 文 件 句 柄 调 用 write() 函 数, 以 向 目 标 文 件 写 入 之 前 那 条 zlib 压 缩 过 的 内 容 : >> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38] => ".git/objects/bd/9dbf5aae1a3862dd b20206e5fc37" >> require 'fileutils' => true >> FileUtils.mkdir_p(File.dirname(path)) => ".git/objects/bd" >> File.open(path, 'w') { f f.write zlib_content } => 32 就 是 这 样 你 已 创 建 了 一 个 有 效 的 Git 数 据 对 象 所 有 的 Git 对 象 均 以 这 种 方 式 存 储, 区 别 仅 在 于 类 型 标 识 另 两 种 对 象 类 型 的 头 部 信 息 以 字 符 串 commit 或 tree 开 头, 而 不 是 blob 另 外, 虽 然 数 据 对 象 的 内 容 几 乎 可 以 是 任 何 东 西, 但 提 交 对 象 和 树 对 象 的 内 容 却 有 各 自 固 定 的 格 式

432 Git 引 用 我 们 可 以 借 助 类 似 于 git log 1a410e 这 样 的 命 令 来 浏 览 完 整 的 提 交 历 史, 但 为 了 能 遍 历 那 段 历 史 从 而 找 到 所 有 相 关 对 象, 你 仍 须 记 住 1a410e 是 最 后 一 个 提 交 我 们 需 要 一 个 文 件 来 保 存 SHA-1 值, 并 给 文 件 起 一 个 简 单 的 名 字, 然 后 用 这 个 名 字 指 针 来 替 代 原 始 的 SHA-1 值 在 Git 里, 这 样 的 文 件 被 称 为 引 用 (references, 或 缩 写 为 refs) ; 你 可 以 在.git/refs 目 录 下 找 到 这 类 含 有 SHA-1 值 的 文 件 在 目 前 的 项 目 中, 这 个 目 录 没 有 包 含 任 何 文 件, 但 它 包 含 了 一 个 简 单 的 目 录 结 构 : $ find.git/refs.git/refs.git/refs/heads.git/refs/tags $ find.git/refs -type f 若 要 创 建 一 个 新 引 用 来 帮 助 记 忆 最 新 提 交 所 在 的 位 置, 从 技 术 上 讲 我 们 只 需 简 单 地 做 如 下 操 作 : $ echo "1a410efbd13591db ebc7a059dd55cfe9" >.git/refs/heads/master 现 在, 你 就 可 以 在 Git 命 令 中 使 用 这 个 刚 创 建 的 新 引 用 来 代 替 SHA-1 值 了 : $ git log --pretty=oneline master 1a410efbd13591db ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 我 们 不 提 倡 直 接 编 辑 引 用 文 件 如 果 想 更 新 某 个 引 用,Git 提 供 了 一 个 更 加 安 全 的 命 令 update-ref 来 完 成 此 事 : $ git update-ref refs/heads/master 1a410efbd13591db ebc7a059dd55cfe9 这 基 本 就 是 Git 分 支 的 本 质 : 一 个 指 向 某 一 系 列 提 交 之 首 的 指 针 或 引 用 若 想 在 第 二 个 提 交 上 创 建 一 个 分 支, 可 以 这 么 做 : $ git update-ref refs/heads/test cac0ca 这 个 分 支 将 只 包 含 从 第 二 个 提 交 开 始 往 前 追 溯 的 记 录 :

433 $ git log --pretty=oneline test cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 至 此, 我 们 的 Git 数 据 库 从 概 念 上 看 起 来 像 这 样 : Figure 152. 包 含 分 支 引 用 的 Git 目 录 对 象 当 运 行 类 似 于 git branch (branchname) 这 样 的 命 令 时,Git 实 际 上 会 运 行 update-ref 命 令, 取 得 当 前 所 在 分 支 最 新 提 交 对 应 的 SHA-1 值, 并 将 其 加 入 你 想 要 创 建 的 任 何 新 引 用 中 HEAD 引 用 现 在 的 问 题 是, 当 你 执 行 git branch (branchname) 时,Git 如 何 知 道 最 新 提 交 的 SHA-1 值 呢? 答 案 是 HEAD 文 件 HEAD 文 件 是 一 个 符 号 引 用 (symbolic reference), 指 向 目 前 所 在 的 分 支 所 谓 符 号 引 用, 意 味 着 它 并 不 像 普 通 引 用 那 样 包 含 一 个 SHA-1 值 它 是 一 个 指 向 其 他 引 用 的 指 针 如 果 查 看 HEAD 文 件 的 内 容, 一 般 而 言 我 们 看 到 的 类 似 这 样 : $ cat.git/head ref: refs/heads/master 如 果 执 行 git checkout test,git 会 像 这 样 更 新 HEAD 文 件 :

434 $ cat.git/head ref: refs/heads/test 当 我 们 执 行 git commit 时, 该 命 令 会 创 建 一 个 提 交 对 象, 并 用 HEAD 文 件 中 那 个 引 用 所 指 向 的 SHA-1 值 设 置 其 父 提 交 字 段 你 也 可 以 手 动 编 辑 该 文 件, 然 而 同 样 存 在 一 个 更 安 全 的 命 令 来 完 成 此 事 :symbolic-ref 可 以 借 助 此 命 令 来 查 看 HEAD 引 用 对 应 的 值 : $ git symbolic-ref HEAD refs/heads/master 同 样 可 以 设 置 HEAD 引 用 的 值 : $ git symbolic-ref HEAD refs/heads/test $ cat.git/head ref: refs/heads/test 不 能 把 符 号 引 用 设 置 为 一 个 不 符 合 引 用 格 式 的 值 : $ git symbolic-ref HEAD test fatal: Refusing to point HEAD outside of refs/ 标 签 引 用 前 文 我 们 刚 讨 论 过 Git 的 三 种 主 要 对 象 类 型, 事 实 上 还 有 第 四 种 标 签 对 象 (tag object) 非 常 类 似 于 一 个 提 交 对 象 它 包 含 一 个 标 签 创 建 者 信 息 一 个 日 期 一 段 注 释 信 息, 以 及 一 个 指 针 主 要 的 区 别 在 于, 标 签 对 象 通 常 指 向 一 个 提 交 对 象, 而 不 是 一 个 树 对 象 它 像 是 一 个 永 不 移 动 的 分 支 引 用 永 远 指 向 同 一 个 提 交 对 象, 只 不 过 给 这 个 提 交 对 象 加 上 一 个 更 友 好 的 名 字 罢 了 正 如 Git 基 础 中 所 讨 论 的 那 样, 存 在 两 种 类 型 的 标 签 : 附 注 标 签 和 轻 量 标 签 可 以 像 这 样 创 建 一 个 轻 量 标 签 : $ git update-ref refs/tags/v1.0 cac0cab538b970a37ea1e769cbbde608743bc96d 这 就 是 轻 量 标 签 的 全 部 内 容 一 个 固 定 的 引 用 然 而, 一 个 附 注 标 签 则 更 复 杂 一 些 若 要 创 建 一 个 附 注 标 签,Git 会 创 建 一 个 标 签 对 象, 并 记 录 一 个 引 用 来 指 向 该 标 签 对 象, 而 不 是 直 接 指 向 提 交 对 象 可 以 通 过 创 建 一 个 附 注 标 签 来 验 证 这 个 过 程 (-a 选 项 指 定 了 要 创 建 的 是 一 个 附 注 标 签 ):

435 $ git tag -a v1.1 1a410efbd13591db ebc7a059dd55cfe9 -m 'test tag' 下 面 是 上 述 过 程 所 建 标 签 对 象 的 SHA-1 值 : $ cat.git/refs/tags/v f37f7b0fb9444f35a9bf50de191beadc2 现 在 对 该 SHA-1 值 运 行 cat-file 命 令 : $ git cat-file -p f37f7b0fb9444f35a9bf50de191beadc2 object 1a410efbd13591db ebc7a059dd55cfe9 type commit tag v1.1 tagger Scott Chacon <schacon@gmail.com> Sat May 23 16:48: test tag 我 们 注 意 到,object 条 目 指 向 我 们 打 了 标 签 的 那 个 提 交 对 象 的 SHA-1 值 另 外 要 注 意 的 是, 标 签 对 象 并 非 必 须 指 向 某 个 提 交 对 象 ; 你 可 以 对 任 意 类 型 的 Git 对 象 打 标 签 例 如, 在 Git 源 码 中, 项 目 维 护 者 将 他 们 的 GPG 公 钥 添 加 为 一 个 数 据 对 象, 然 后 对 这 个 对 象 打 了 一 个 标 签 可 以 克 隆 一 个 Git 版 本 库, 然 后 通 过 执 行 下 面 的 命 令 来 在 这 个 版 本 库 中 查 看 上 述 公 钥 : $ git cat-file blob junio-gpg-pub Linux 内 核 版 本 库 同 样 有 一 个 不 指 向 提 交 对 象 的 标 签 对 象 首 个 被 创 建 的 标 签 对 象 所 指 向 的 是 最 初 被 引 入 版 本 库 的 那 份 内 核 源 码 所 对 应 的 树 对 象 远 程 引 用 我 们 将 看 到 的 第 三 种 引 用 类 型 是 远 程 引 用 (remote reference) 如 果 你 添 加 了 一 个 远 程 版 本 库 并 对 其 执 行 过 推 送 操 作,Git 会 记 录 下 最 近 一 次 推 送 操 作 时 每 一 个 分 支 所 对 应 的 值, 并 保 存 在 refs/remotes 目 录 下 例 如, 你 可 以 添 加 一 个 叫 做 origin 的 远 程 版 本 库, 然 后 把 master 分 支 推 送 上 去 :

436 $ git remote add origin $ git push origin master Counting objects: 11, done. Compressing objects: 100% (5/5), done. Writing objects: 100% (7/7), 716 bytes, done. Total 7 (delta 2), reused 4 (delta 1) To git@github.com:schacon/simplegit-progit.git a11bef0..ca82a6d master -> master 此 时, 如 果 查 看 refs/remotes/origin/master 文 件, 可 以 发 现 origin 远 程 版 本 库 的 master 分 支 所 对 应 的 SHA-1 值, 就 是 最 近 一 次 与 服 务 器 通 信 时 本 地 master 分 支 所 对 应 的 SHA-1 值 : $ cat.git/refs/remotes/origin/master ca82a6dff817ec66f a 远 程 引 用 和 分 支 ( 位 于 refs/heads 目 录 下 的 引 用 ) 之 间 最 主 要 的 区 别 在 于, 远 程 引 用 是 只 读 的 虽 然 可 以 git checkout 到 某 个 远 程 引 用, 但 是 Git 并 不 会 将 HEAD 引 用 指 向 该 远 程 引 用 因 此, 你 永 远 不 能 通 过 commit 命 令 来 更 新 远 程 引 用 Git 将 这 些 远 程 引 用 作 为 记 录 远 程 服 务 器 上 各 分 支 最 后 已 知 位 置 状 态 的 书 签 来 管 理 包 文 件 让 我 们 重 新 回 到 示 例 Git 版 本 库 的 对 象 数 据 库 目 前 为 止, 可 以 看 到 有 11 个 对 象 4 个 数 据 对 象 3 个 树 对 象 3 个 提 交 对 象 和 1 个 标 签 对 象 : $ find.git/objects -type f.git/objects/01/55eb a0f03eb265b69f5a2d56f341 # tree 2.git/objects/1a/410efbd13591db ebc7a059dd55cfe9 # commit 3.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2.git/objects/3c/4e9cd789d88d8d89c c3585e41b0e614 # tree 3.git/objects/83/baae61804e65cc73a7201a c76066a30 # test.txt v1.git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1.git/objects/fa/49b ad f2a75f74e3671e92 # new.txt.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1 Git 使 用 zlib 压 缩 这 些 文 件 的 内 容, 而 且 我 们 并 没 有 存 储 太 多 东 西, 所 以 上 文 中 的 文 件 一 共 只 占 用 了 925 字 节 接 下 来, 我 们 会 指 引 你 添 加 一 些 大 文 件 到 版 本 库 中, 以 此 展 示 Git 的 一 个 很 有 趣 的 功 能 为 了 便 于 展 示, 我 们 要 把 之 前 在 Grit 库 中 用 到 过 的 repo.rb 文 件 添 加 进 来 这 是 一 个 大 小 约 为 22K 的 源 代 码 文 件 :

437 $ curl > repo.rb $ git add repo.rb $ git commit -m 'added repo.rb' [master 484a592] added repo.rb 3 files changed, 709 insertions(+), 2 deletions(-) delete mode bak/test.txt create mode repo.rb rewrite test.txt (100%) 如 果 你 查 看 生 成 的 树 对 象, 可 以 看 到 repo.rb 文 件 对 应 的 数 据 对 象 的 SHA-1 值 : $ git cat-file -p master^{tree} blob fa49b ad f2a75f74e3671e92 new.txt blob 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 repo.rb blob e3f094f522629ae358806b17daf78246c27c007b test.txt 接 下 来 你 可 以 使 用 git cat-file 命 令 查 看 这 个 对 象 有 多 大 : $ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d 现 在, 稍 微 修 改 这 个 文 件, 然 后 看 看 会 发 生 什 么 : $ echo '# testing' >> repo.rb $ git commit -am 'modified repo a bit' [master 2431da6] modified repo.rb a bit 1 file changed, 1 insertion(+) 查 看 这 个 提 交 生 成 的 树 对 象, 你 会 看 到 一 些 有 趣 的 东 西 : $ git cat-file -p master^{tree} blob fa49b ad f2a75f74e3671e92 new.txt blob b042a60ef7dff760008df33cee372b945b6e884e repo.rb blob e3f094f522629ae358806b17daf78246c27c007b test.txt repo.rb 对 应 一 个 与 之 前 完 全 不 同 的 数 据 对 象, 这 意 味 着, 虽 然 你 只 是 在 一 个 400 行 的 文 件 后 面 加 入 一 行 新 内 容,Git 也 会 用 一 个 全 新 的 对 象 来 存 储 新 的 文 件 内 容 :

438 $ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e 你 的 磁 盘 上 现 在 有 两 个 几 乎 完 全 相 同 大 小 均 为 22K 的 对 象 如 果 Git 只 完 整 保 存 其 中 一 个, 再 保 存 另 一 个 对 象 与 之 前 版 本 的 差 异 内 容, 岂 不 更 好? 事 实 上 Git 可 以 那 样 做 Git 最 初 向 磁 盘 中 存 储 对 象 时 所 使 用 的 格 式 被 称 为 松 散 (loose) 对 象 格 式 但 是,Git 会 时 不 时 地 将 多 个 这 些 对 象 打 包 成 一 个 称 为 包 文 件 (packfile) 的 二 进 制 文 件, 以 节 省 空 间 和 提 高 效 率 当 版 本 库 中 有 太 多 的 松 散 对 象, 或 者 你 手 动 执 行 git gc 命 令, 或 者 你 向 远 程 服 务 器 执 行 推 送 时,Git 都 会 这 样 做 要 看 到 打 包 过 程, 你 可 以 手 动 执 行 git gc 命 令 让 Git 对 对 象 进 行 打 包 : $ git gc Counting objects: 18, done. Delta compression using up to 8 threads. Compressing objects: 100% (14/14), done. Writing objects: 100% (18/18), done. Total 18 (delta 3), reused 0 (delta 0) 这 个 时 候 再 查 看 objects 目 录, 你 会 发 现 大 部 分 的 对 象 都 不 见 了, 与 此 同 时 出 现 了 一 对 新 文 件 : $ find.git/objects -type f.git/objects/bd/9dbf5aae1a3862dd b20206e5fc37.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4.git/objects/info/packs.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e idx.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e pack 仍 保 留 着 的 几 个 对 象 是 未 被 任 何 提 交 记 录 引 用 的 数 据 对 象 在 此 例 中 是 你 之 前 创 建 的 what is up, doc? 和 test content 这 两 个 示 例 数 据 对 象 因 为 你 从 没 将 它 们 添 加 至 任 何 提 交 记 录 中, 所 以 Git 认 为 它 们 是 摇 摆 (dangling) 的, 不 会 将 它 们 打 包 进 新 生 成 的 包 文 件 中 剩 下 的 文 件 是 新 创 建 的 包 文 件 和 一 个 索 引 包 文 件 包 含 了 刚 才 从 文 件 系 统 中 移 除 的 所 有 对 象 的 内 容 索 引 文 件 包 含 了 包 文 件 的 偏 移 信 息, 我 们 通 过 索 引 文 件 就 可 以 快 速 定 位 任 意 一 个 指 定 对 象 有 意 思 的 是 运 行 gc 命 令 前 磁 盘 上 的 对 象 大 小 约 为 22K, 而 这 个 新 生 成 的 包 文 件 大 小 仅 有 7K 通 过 打 包 对 象 减 少 了 ⅔ 的 磁 盘 占 用 空 间 Git 是 如 何 做 到 这 点 的?Git 打 包 对 象 时, 会 查 找 命 名 及 大 小 相 近 的 文 件, 并 只 保 存 文 件 不 同 版 本 之 间 的 差 异 内 容 你 可 以 查 看 包 文 件, 观 察 它 是 如 何 节 省 空 间 的 git verify-pack 这 个 底 层 命 令 可 以 让 你 查 看 已 打 包 的 内 容 :

439 $ git verify-pack -v.git/objects/pack/pack- 978e03944f5c581011e6998cd0e9e idx 2431da a4d72e260db3bf7b0f587bbc1 commit bcdaff ab1c0812ce0e07fa7d26a96d7 commit d02664cb23ed55b c7ad5d0a3deb90 commit a18b7613d1281e a83eb8fde3d687 commit a802e94d727c820a9024e14a1fc2 commit ce72005e2edff522fde85d52a65df9b commit d368d0ac0678cbe6cce505be58126d e54 tag fe879577cb8cffcdf e310dd7d239b tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree deef2e1b e50a2ea2ddb5ba6c58c4506 tree d982c7cb2c2a972ee391a85da481fc1f9127a01d tree \ deef2e1b e50a2ea2ddb5ba6c58c4506 3c4e9cd789d88d8d89c c3585e41b0e614 tree \ deef2e1b e50a2ea2ddb5ba6c58c eb a0f03eb265b69f5a2d56f341 tree baae61804e65cc73a7201a c76066a30 blob fa49b ad f2a75f74e3671e92 blob b042a60ef7dff760008df33cee372b945b6e884e blob b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob \ b042a60ef7dff760008df33cee372b945b6e884e 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob non delta: 15 objects chain length = 1: 3 objects.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e pack: ok 此 处,033b4 这 个 数 据 对 象 ( 即 repo.rb 文 件 的 第 一 个 版 本, 如 果 你 还 记 得 的 话 ) 引 用 了 数 据 对 象 b042a, 即 该 文 件 的 第 二 个 版 本 命 令 输 出 内 容 的 第 三 列 显 示 的 是 各 个 对 象 在 包 文 件 中 的 大 小, 可 以 看 到 b042a 占 用 了 22K 空 间, 而 033b4 仅 占 用 9 字 节 同 样 有 趣 的 地 方 在 于, 第 二 个 版 本 完 整 保 存 了 文 件 内 容, 而 原 始 的 版 本 反 而 是 以 差 异 方 式 保 存 的 这 是 因 为 大 部 分 情 况 下 需 要 快 速 访 问 文 件 的 最 新 版 本 最 妙 之 处 是 你 可 以 随 时 重 新 打 包 Git 时 常 会 自 动 对 仓 库 进 行 重 新 打 包 以 节 省 空 间 当 然 你 也 可 以 随 时 手 动 执 行 git gc 命 令 来 这 么 做 引 用 规 格 纵 观 全 书, 我 们 已 经 使 用 过 一 些 诸 如 远 程 分 支 到 本 地 引 用 的 简 单 映 射 方 式, 但 这 种 映 射 可 以 更 复 杂 假 设 你 添 加 了 这 样 一 个 远 程 版 本 库 : $ git remote add origin 上 述 命 令 会 在 你 的.git/config 文 件 中 添 加 一 个 小 节, 并 在 其 中 指 定 远 程 版 本 库 的 名 称 (origin) URL 和 一 个 用 于 获 取 操 作 的 引 用 规 格 (refspec):

440 [remote "origin"] url = fetch = +refs/heads/*:refs/remotes/origin/* 引 用 规 格 的 格 式 由 一 个 可 选 的 + 号 和 紧 随 其 后 的 <src>:<dst> 组 成, 其 中 <src> 是 一 个 模 式 (pattern), 代 表 远 程 版 本 库 中 的 引 用 ;<dst> 是 那 些 远 程 引 用 在 本 地 所 对 应 的 位 置 + 号 告 诉 Git 即 使 在 不 能 快 进 的 情 况 下 也 要 ( 强 制 ) 更 新 引 用 默 认 情 况 下, 引 用 规 格 由 git remote add 命 令 自 动 生 成, Git 获 取 服 务 器 中 refs/heads/ 下 面 的 所 有 引 用, 并 将 它 写 入 到 本 地 的 refs/remotes/origin/ 中 所 以, 如 果 服 务 器 上 有 一 个 master 分 支, 我 们 可 以 在 本 地 通 过 下 面 这 种 方 式 来 访 问 该 分 支 上 的 提 交 记 录 : $ git log origin/master $ git log remotes/origin/master $ git log refs/remotes/origin/master 上 面 的 三 个 命 令 作 用 相 同, 因 为 Git 会 把 它 们 都 扩 展 成 refs/remotes/origin/master 如 果 想 让 Git 每 次 只 拉 取 远 程 的 master 分 支, 而 不 是 所 有 分 支, 可 以 把 ( 引 用 规 格 的 ) 获 取 那 一 行 修 改 为 : fetch = +refs/heads/master:refs/remotes/origin/master 这 仅 是 针 对 该 远 程 版 本 库 的 git fetch 操 作 的 默 认 引 用 规 格 如 果 有 某 些 只 希 望 被 执 行 一 次 的 操 作, 我 们 也 可 以 在 命 令 行 指 定 引 用 规 格 若 要 将 远 程 的 master 分 支 拉 到 本 地 的 origin/mymaster 分 支, 可 以 运 行 : $ git fetch origin master:refs/remotes/origin/mymaster 你 也 可 以 指 定 多 个 引 用 规 格 在 命 令 行 中, 你 可 以 按 照 如 下 的 方 式 拉 取 多 个 分 支 : $ git fetch origin master:refs/remotes/origin/mymaster \ topic:refs/remotes/origin/topic From git@github.com:schacon/simplegit! [rejected] master -> origin/mymaster (non fast forward) * [new branch] topic -> origin/topic 在 这 个 例 子 中, 对 master 分 支 的 拉 取 操 作 被 拒 绝, 因 为 它 不 是 一 个 可 以 快 进 的 引 用 我 们 可 以 通 过 在 引 用 规 格 之 前 指 定 + 号 来 覆 盖 该 规 则 你 也 可 以 在 配 置 文 件 中 指 定 多 个 用 于 获 取 操 作 的 引 用 规 格 如 果 想 在 每 次 获 取 时 都 包 括 master 和

441 experiment 分 支, 添 加 如 下 两 行 : [remote "origin"] url = fetch = +refs/heads/master:refs/remotes/origin/master fetch = +refs/heads/experiment:refs/remotes/origin/experiment 我 们 不 能 在 模 式 中 使 用 部 分 通 配 符, 所 以 像 下 面 这 样 的 引 用 规 格 是 不 合 法 的 : fetch = +refs/heads/qa*:refs/remotes/origin/qa* 但 我 们 可 以 使 用 命 名 空 间 ( 或 目 录 ) 来 达 到 类 似 目 的 假 设 你 有 一 个 QA 团 队, 他 们 推 送 了 一 系 列 分 支, 同 时 你 只 想 要 获 取 master 和 QA 团 队 的 所 有 分 支 而 不 关 心 其 他 任 何 分 支, 那 么 可 以 使 用 如 下 配 置 : [remote "origin"] url = fetch = +refs/heads/master:refs/remotes/origin/master fetch = +refs/heads/qa/*:refs/remotes/origin/qa/* 如 果 项 目 的 工 作 流 很 复 杂, 有 QA 团 队 推 送 分 支 开 发 人 员 推 送 分 支 集 成 团 队 推 送 并 且 在 远 程 分 支 上 展 开 协 作, 你 就 可 以 像 这 样 ( 在 本 地 ) 为 这 些 分 支 创 建 各 自 的 命 名 空 间, 非 常 方 便 引 用 规 格 推 送 像 上 面 这 样 从 远 程 版 本 库 获 取 已 在 命 名 空 间 中 的 引 用 当 然 很 棒, 但 QA 团 队 最 初 应 该 如 何 将 他 们 的 分 支 放 入 远 程 的 qa/ 命 名 空 间 呢? 我 们 可 以 通 过 引 用 规 格 推 送 来 完 成 这 个 任 务 如 果 QA 团 队 想 把 他 们 的 master 分 支 推 送 到 远 程 服 务 器 的 qa/master 分 支 上, 可 以 运 行 : $ git push origin master:refs/heads/qa/master 如 果 他 们 希 望 Git 每 次 运 行 git push origin 时 都 像 上 面 这 样 推 送, 可 以 在 他 们 的 配 置 文 件 中 添 加 一 条 push 值 : [remote "origin"] url = fetch = +refs/heads/*:refs/remotes/origin/* push = refs/heads/master:refs/heads/qa/master

442 正 如 刚 才 所 指 出 的, 这 会 让 git push origin 默 认 把 本 地 master 分 支 推 送 到 远 程 qa/master 分 支 删 除 引 用 你 还 可 以 借 助 类 似 下 面 的 命 令 通 过 引 用 规 格 从 远 程 服 务 器 上 删 除 引 用 : $ git push origin :topic 因 为 引 用 规 格 ( 的 格 式 ) 是 <src>:<dst>, 所 以 上 述 命 令 把 <src> 留 空, 意 味 着 把 远 程 版 本 库 的 topic 分 支 定 义 为 空 值, 也 就 是 删 除 它 传 输 协 议 Git 可 以 通 过 两 种 主 要 的 方 式 在 版 本 库 之 间 传 输 数 据 : 哑 (dumb) 协 议 和 智 能 (smart) 协 议 本 节 将 会 带 你 快 速 浏 览 这 两 种 协 议 的 运 作 方 式 哑 协 议 如 果 你 正 在 架 设 一 个 基 于 HTTP 协 议 的 只 读 版 本 库, 一 般 而 言 这 种 情 况 下 使 用 的 就 是 哑 协 议 这 个 协 议 之 所 以 被 称 为 哑 协 议, 是 因 为 在 传 输 过 程 中, 服 务 端 不 需 要 有 针 对 Git 特 有 的 代 码 ; 抓 取 过 程 是 一 系 列 HTTP 的 GET 请 求, 这 种 情 况 下, 客 户 端 可 以 推 断 出 服 务 端 Git 仓 库 的 布 局 NOTE 现 在 已 经 很 少 使 用 哑 协 议 了 使 用 哑 协 议 的 版 本 库 很 难 保 证 安 全 性 和 私 有 化, 所 以 大 多 数 Git 服 务 器 宿 主 ( 包 括 云 端 和 本 地 ) 都 会 拒 绝 使 用 它 一 般 情 况 下 都 建 议 使 用 智 能 协 议, 我 们 会 在 后 面 进 行 介 绍 让 我 们 通 过 simplegit 版 本 库 来 看 看 http-fetch 的 过 程 : $ git clone 它 做 的 第 一 件 事 就 是 拉 取 info/refs 文 件 这 个 文 件 是 通 过 update-server-info 命 令 生 成 的, 这 也 解 释 了 在 使 用 HTTP 传 输 时, 必 须 把 它 设 置 为 post-receive 钩 子 的 原 因 : => GET info/refs ca82a6dff817ec66f a refs/heads/master 现 在, 你 得 到 了 一 个 远 程 引 用 和 SHA-1 值 的 列 表 接 下 来, 你 要 确 定 HEAD 引 用 是 什 么, 这 样 你 就 知 道 在 完 成 后 应 该 被 检 出 到 工 作 目 录 的 内 容 :

443 => GET HEAD ref: refs/heads/master 这 说 明 在 完 成 抓 取 后, 你 需 要 检 出 master 分 支 这 时, 你 就 可 以 开 始 遍 历 处 理 了 因 为 你 是 从 info/refs 文 件 中 所 提 到 的 ca82a6 提 交 对 象 开 始 的, 所 以 你 的 首 要 操 作 是 获 取 它 : => GET objects/ca/82a6dff817ec66f a (179 bytes of binary data) 你 取 回 了 一 个 对 象 这 是 一 个 在 服 务 端 以 松 散 格 式 保 存 的 对 象, 是 你 通 过 使 用 静 态 HTTP GET 请 求 获 取 的 你 可 以 使 用 zlib 解 压 缩 它, 去 除 其 头 部, 查 看 提 交 记 录 的 内 容 : $ git cat-file -p ca82a6dff817ec66f a tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon <schacon@gmail.com> committer Scott Chacon <schacon@gmail.com> changed the version number 接 下 来, 你 还 要 再 获 取 两 个 对 象, 一 个 是 树 对 象 cfda3b, 它 包 含 有 我 们 刚 刚 获 取 的 提 交 对 象 所 指 向 的 内 容, 另 一 个 是 它 的 父 提 交 085bb3: => GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 (179 bytes of data) 这 样 就 取 得 了 你 的 下 一 个 提 交 对 象 再 抓 取 树 对 象 : => GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf (404 - Not Found) 噢 看 起 来 这 个 树 对 象 在 服 务 端 并 不 以 松 散 格 式 对 象 存 在, 所 以 你 得 到 了 一 个 404 响 应, 代 表 在 HTTP 服 务 端 没 有 找 到 该 对 象 这 有 好 几 个 可 能 的 原 因 这 个 对 象 可 能 在 替 代 版 本 库 里 面, 或 者 在 包 文 件 里 面 Git 会 首 先 检 查 所 有 列 出 的 替 代 版 本 库 : => GET objects/info/http-alternates (empty file)

444 如 果 这 返 回 了 一 个 包 含 替 代 版 本 库 URL 的 列 表, 那 么 Git 就 会 去 那 些 地 址 检 查 松 散 格 式 对 象 和 文 件 这 是 一 种 能 让 派 生 项 目 共 享 对 象 以 节 省 磁 盘 的 好 方 法 然 而, 在 这 个 例 子 中, 没 有 列 出 可 用 的 替 代 版 本 库 所 以 你 所 需 要 的 对 象 肯 定 在 某 个 包 文 件 中 要 检 查 服 务 端 有 哪 些 可 用 的 包 文 件, 你 需 要 获 取 objects/info/packs 文 件, 这 里 面 有 一 个 包 文 件 列 表 ( 它 也 是 通 过 执 行 updateserver-info 所 生 成 的 ): => GET objects/info/packs P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack 服 务 端 只 有 一 个 包 文 件, 所 以 你 要 的 对 象 显 然 就 在 里 面 但 是 你 要 先 检 查 它 的 索 引 文 件 以 确 认 即 使 服 务 端 有 多 个 包 文 件, 这 也 是 很 有 用 的, 因 为 这 样 你 就 可 以 知 道 你 所 需 要 的 对 象 是 在 哪 一 个 包 文 件 里 面 : => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx (4k of binary data) 现 在 你 有 这 个 包 文 件 的 索 引, 你 可 以 查 看 你 要 的 对 象 是 否 在 里 面 因 为 索 引 文 件 列 出 了 这 个 包 文 件 所 包 含 的 所 有 对 象 的 SHA-1 值, 和 该 对 象 存 在 于 包 文 件 中 的 偏 移 量 你 的 对 象 就 在 这 里, 接 下 来 就 是 获 取 整 个 包 文 件 : => GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack (13k of binary data) 现 在 你 也 有 了 你 的 树 对 象, 你 可 以 继 续 在 提 交 记 录 上 漫 游 它 们 全 部 都 在 这 个 你 刚 下 载 的 包 文 件 里 面, 所 以 你 不 用 继 续 向 服 务 端 请 求 更 多 下 载 了 Git 会 将 开 始 时 下 载 的 HEAD 引 用 所 指 向 的 master 分 支 检 出 到 工 作 目 录 智 能 协 议 哑 协 议 虽 然 很 简 单 但 效 率 略 低, 且 它 不 能 从 客 户 端 向 服 务 端 发 送 数 据 智 能 协 议 是 更 常 用 的 传 送 数 据 的 方 法, 但 它 需 要 在 服 务 端 运 行 一 个 进 程, 而 这 也 是 Git 的 智 能 之 处 它 可 以 读 取 本 地 数 据, 理 解 客 户 端 有 什 么 和 需 要 什 么, 并 为 它 生 成 合 适 的 包 文 件 总 共 有 两 组 进 程 用 于 传 输 数 据, 它 们 分 别 负 责 上 传 和 下 载 数 据 上 传 数 据 为 了 上 传 数 据 至 远 端,Git 使 用 send-pack 和 receive-pack 进 程 运 行 在 客 户 端 上 的 send-pack 进 程 连 接 到 远 端 运 行 的 receive-pack 进 程 SSH 举 例 来 说, 在 项 目 中 使 用 命 令 git push origin master 时, origin 是 由 基 于 SSH 协 议 的 URL 所 定 义 的 Git 会 运 行 send-pack 进 程, 它 会 通 过 SSH 连 接 你 的 服 务 器 它 会 尝 试 通 过 SSH 在 服 务 端 执 行 命 令, 就 像 这 样 :

445 $ ssh -x "git-receive-pack 'simplegit-progit.git'" 00a5ca82a6dff817ec66f a refs/heads/master report-status \ delete-refs side-band-64k quiet ofs-delta \ agent=git/2:2.1.1+github-607-gfba4028 delete-refs 0000 git-receive-pack 命 令 会 立 即 为 它 所 拥 有 的 每 一 个 引 用 发 送 一 行 响 应 在 这 个 例 子 中, 就 只 有 master 分 支 和 它 的 SHA-1 值 第 一 行 响 应 中 也 包 含 了 一 个 服 务 端 能 力 的 列 表 ( 这 里 是 report-status delete-refs 和 一 些 其 它 的, 包 括 客 户 端 的 识 别 码 ) 每 一 行 以 一 个 四 位 的 十 六 进 制 值 开 始, 用 于 指 明 本 行 的 长 度 你 看 到 第 一 行 以 005b 开 始, 这 在 十 六 进 制 中 表 示 91, 意 味 着 第 一 行 有 91 字 节 下 一 行 以 003e 起 始, 也 就 是 62, 所 以 下 面 需 要 读 取 62 字 节 再 下 一 行 是 0000, 表 示 服 务 端 已 完 成 了 发 送 引 用 列 表 过 程 现 在 它 知 道 了 服 务 端 的 状 态, 你 的 send-pack 进 程 会 判 断 哪 些 提 交 记 录 是 它 所 拥 有 但 服 务 端 没 有 的 sendpack 会 告 知 receive-pack 这 次 推 送 将 会 更 新 的 各 个 引 用 举 个 例 子, 如 果 你 正 在 更 新 master 分 支, 并 且 增 加 experiment 分 支, 这 个 send-pack 的 响 应 将 会 是 像 这 样 : 0076ca82a6dff817ec66f a b64cf874c3557a0f3547bd83b3ff6 \ refs/heads/master report-status 006c cdfdb42577e f8cfeacdbabc092bf63e8d \ refs/heads/experiment 0000 Git 会 为 每 一 个 将 要 更 新 的 引 用 发 送 一 行 数 据, 包 括 该 行 长 度, 旧 SHA-1 值, 新 SHA-1 值 和 将 要 更 新 的 引 用 第 一 行 也 包 括 了 客 户 端 的 能 力 这 里 的 全 为 0 的 SHA-1 值 表 示 之 前 没 有 过 这 个 引 用 因 为 你 正 要 添 加 新 的 experiment 引 用 删 除 引 用 时, 将 会 看 到 相 反 的 情 况 : 右 边 的 SHA-1 值 全 为 0 接 下 来, 客 户 端 会 发 送 一 个 包 文 件, 它 包 含 了 所 有 服 务 端 还 没 有 的 对 象 最 后, 服 务 端 会 以 成 功 ( 或 失 败 ) 响 应 : 000eunpack ok HTTP(S) HTTPS 与 HTTP 相 比 较, 除 了 在 握 手 过 程 略 有 不 同 外, 其 他 基 本 相 似 连 接 是 从 下 面 这 个 请 求 开 始 的 :

446 => GET 001f# service=git-receive-pack 00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master reportstatus \ delete-refs side-band-64k quiet ofs-delta \ agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e 0000 这 完 成 了 客 户 端 和 服 务 端 的 第 一 次 数 据 交 换 接 下 来 客 户 端 发 起 另 一 个 请 求, 这 次 是 一 个 POST 请 求, 这 个 请 求 中 包 含 了 git-upload-pack 提 供 的 数 据 => POST 这 个 POST 请 求 的 内 容 是 send-pack 的 输 出 和 相 应 的 包 文 件 服 务 端 在 收 到 请 求 后 相 应 地 作 出 成 功 或 失 败 的 HTTP 响 应 下 载 数 据 当 你 在 下 载 数 据 时, fetch-pack 和 upload-pack 进 程 就 起 作 用 了 客 户 端 启 动 fetch-pack 进 程, 连 接 至 远 端 的 upload-pack 进 程, 以 协 商 后 续 传 输 的 数 据 SSH 如 果 你 通 过 SSH 使 用 抓 取 功 能,fetch-pack 会 像 这 样 运 行 : $ ssh -x git@server "git-upload-pack 'simplegit-progit.git'" 在 fetch-pack 连 接 后,upload-pack 会 返 回 类 似 下 面 的 内 容 : 00dfca82a6dff817ec66f a HEAD multi_ack thin-pack \ side-band side-band-64k ofs-delta shallow no-progress include-tag \ multi_ack_detailed symref=head:refs/heads/master \ agent=git/2:2.1.1+github-607-gfba fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master 0000 这 与 receive-pack 的 响 应 很 相 似, 但 是 这 里 所 包 含 的 能 力 是 不 同 的 而 且 它 还 包 含 HEAD 引 用 所 指 向 内 容 (symref=head:refs/heads/master), 这 样 如 果 客 户 端 执 行 的 是 克 隆, 它 就 会 知 道 要 检 出 什 么 这 时 候,fetch-pack 进 程 查 看 它 自 己 所 拥 有 的 对 象, 并 响 应 want 和 它 需 要 的 对 象 的 SHA-1 值 它 还 会

447 发 送 have 和 所 有 它 已 拥 有 的 对 象 的 SHA-1 值 在 列 表 的 最 后, 它 还 会 发 送 done 以 通 知 upload-pack 进 程 可 以 开 始 发 送 它 所 需 对 象 的 包 文 件 : 003cwant ca82a6dff817ec66f a ofs-delta 0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 0009done 0000 HTTP(S) 抓 取 操 作 的 握 手 需 要 两 个 HTTP 请 求 第 一 个 是 向 和 哑 协 议 中 相 同 的 端 点 发 送 GET 请 求 : => GET $GIT_URL/info/refs?service=git-upload-pack 001e# service=git-upload-pack 00e7ca82a6dff817ec66f a HEAD multi_ack thin-pack \ side-band side-band-64k ofs-delta shallow no-progress include-tag \ multi_ack_detailed no-done symref=head:refs/heads/master \ agent=git/2:2.1.1+github-607-gfba fca82a6dff817ec66f a refs/heads/master 0000 这 和 通 过 SSH 使 用 git-upload-pack 是 非 常 相 似 的, 但 是 第 二 个 数 据 交 换 则 是 一 个 单 独 的 请 求 : => POST $GIT_URL/git-upload-pack HTTP/ want 0a53e9ddeaddad63ad bbf53411d11a7 0032have 441b40d833fdfa93eb2908e faf0ee 这 个 输 出 格 式 还 是 和 前 面 一 样 的 这 个 请 求 的 响 应 包 含 了 所 需 要 的 包 文 件, 并 指 明 成 功 或 失 败 协 议 总 结 这 一 章 节 是 传 输 协 议 的 一 个 概 貌 传 输 协 议 还 有 很 多 其 它 的 特 性, 像 是 multi_ack 或 side-band, 但 是 这 些 内 容 已 经 超 出 了 本 书 的 范 围 我 们 希 望 能 给 你 展 示 客 户 端 和 服 务 端 之 间 的 基 本 交 互 过 程 ; 如 果 你 需 要 更 多 的 相 关 知 识, 你 可 以 参 阅 Git 的 源 代 码 维 护 与 数 据 恢 复 有 的 时 候, 你 需 要 对 仓 库 进 行 清 理 - 使 它 的 结 构 变 得 更 紧 凑, 或 是 对 导 入 的 仓 库 进 行 清 理, 或 是 恢 复 丢 失 的 内 容 这 个 小 节 将 会 介 绍 这 些 情 况 中 的 一 部 分

448 维 护 Git 会 不 定 时 地 自 动 运 行 一 个 叫 做 auto gc 的 命 令 大 多 数 时 候, 这 个 命 令 并 不 会 产 生 效 果 然 而, 如 果 有 太 多 松 散 对 象 ( 不 在 包 文 件 中 的 对 象 ) 或 者 太 多 包 文 件,Git 会 运 行 一 个 完 整 的 git gc 命 令 gc 代 表 垃 圾 回 收, 这 个 命 令 会 做 以 下 事 情 : 收 集 所 有 松 散 对 象 并 将 它 们 放 置 到 包 文 件 中, 将 多 个 包 文 件 合 并 为 一 个 大 的 包 文 件, 移 除 与 任 何 提 交 都 不 相 关 的 陈 旧 对 象 可 以 像 下 面 一 样 手 动 执 行 自 动 垃 圾 回 收 : $ git gc --auto 就 像 上 面 提 到 的, 这 个 命 令 通 常 并 不 会 产 生 效 果 大 约 需 要 7000 个 以 上 的 松 散 对 象 或 超 过 50 个 的 包 文 件 才 能 让 Git 启 动 一 次 真 正 的 gc 命 令 你 可 以 通 过 修 改 gc.auto 与 gc.autopacklimit 的 设 置 来 改 动 这 些 数 值 gc 将 会 做 的 另 一 件 事 是 打 包 你 的 引 用 到 一 个 单 独 的 文 件 假 设 你 的 仓 库 包 含 以 下 分 支 与 标 签 : $ find.git/refs -type f.git/refs/heads/experiment.git/refs/heads/master.git/refs/tags/v1.0.git/refs/tags/v1.1 如 果 你 执 行 了 git gc 命 令,refs 目 录 中 将 不 会 再 有 这 些 文 件 为 了 保 证 效 率 Git 会 将 它 们 移 动 到 名 为.git/packed-refs 的 文 件 中, 就 像 这 样 : $ cat.git/packed-refs # pack-refs with: peeled fully-peeled cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1 ^1a410efbd13591db ebc7a059dd55cfe9 如 果 你 更 新 了 引 用,Git 并 不 会 修 改 这 个 文 件, 而 是 向 refs/heads 创 建 一 个 新 的 文 件 为 了 获 得 指 定 引 用 的 正 确 SHA-1 值,Git 会 首 先 在 refs 目 录 中 查 找 指 定 的 引 用, 然 后 再 到 packed-refs 文 件 中 查 找 所 以, 如 果 你 在 refs 目 录 中 找 不 到 一 个 引 用, 那 么 它 或 许 在 packed-refs 文 件 中 注 意 这 个 文 件 的 最 后 一 行, 它 会 以 ^ 开 头 这 个 符 号 表 示 它 上 一 行 的 标 签 是 附 注 标 签, 那 一 行 是 附 注 标 签 指 向 的 那 个 提 交

449 数 据 恢 复 在 你 使 用 Git 的 时 候, 你 可 能 会 意 外 丢 失 一 次 提 交 通 常 这 是 因 为 你 强 制 删 除 了 正 在 工 作 的 分 支, 但 是 最 后 却 发 现 你 还 需 要 这 个 分 支 ; 亦 或 者 硬 重 置 了 一 个 分 支, 放 弃 了 你 想 要 的 提 交 如 果 这 些 事 情 已 经 发 生, 该 如 何 找 回 你 的 提 交 呢? 下 面 的 例 子 将 硬 重 置 你 的 测 试 仓 库 中 的 master 分 支 到 一 个 旧 的 提 交, 以 此 来 恢 复 丢 失 的 提 交 首 先, 让 我 们 看 看 你 的 仓 库 现 在 在 什 么 地 方 : $ git log --pretty=oneline ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit 484a e19aadb7c cfcdf19a added repo.rb 1a410efbd13591db ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 现 在, 我 们 将 master 分 支 硬 重 置 到 第 三 次 提 交 : $ git reset --hard 1a410efbd13591db ebc7a059dd55cfe9 HEAD is now at 1a410ef third commit $ git log --pretty=oneline 1a410efbd13591db ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 现 在 顶 部 的 两 个 提 交 已 经 丢 失 了 - 没 有 分 支 指 向 这 些 提 交 你 需 要 找 出 最 后 一 次 提 交 的 SHA-1 然 后 增 加 一 个 指 向 它 的 分 支 窍 门 就 是 找 到 最 后 一 次 的 提 交 的 SHA-1 - 但 是 估 计 你 记 不 起 来 了, 对 吗? 最 方 便, 也 是 最 常 用 的 方 法, 是 使 用 一 个 名 叫 git reflog 的 工 具 当 你 正 在 工 作 时,Git 会 默 默 地 记 录 每 一 次 你 改 变 HEAD 时 它 的 值 每 一 次 你 提 交 或 改 变 分 支, 引 用 日 志 都 会 被 更 新 引 用 日 志 (reflog) 也 可 以 通 过 git update-ref 命 令 更 新, 我 们 在 Git 引 用 有 提 到 使 用 这 个 命 令 而 不 是 是 直 接 将 SHA-1 的 值 写 入 引 用 文 件 中 的 原 因 你 可 以 在 任 何 时 候 通 过 执 行 git reflog 命 令 来 了 解 你 曾 经 做 过 什 么 : $ git reflog 1a410ef HEAD@{0}: reset: moving to 1a410ef ab1afef HEAD@{1}: commit: modified repo.rb a bit 484a592 HEAD@{2}: commit: added repo.rb 这 里 可 以 看 到 我 们 已 经 检 出 的 两 次 提 交, 然 而 并 没 有 足 够 多 的 信 息 为 了 使 显 示 的 信 息 更 加 有 用, 我 们 可 以 执 行 git log -g, 这 个 命 令 会 以 标 准 日 志 的 格 式 输 出 引 用 日 志

450 $ git log -g commit 1a410efbd13591db ebc7a059dd55cfe9 Reflog: (Scott Chacon Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:22: third commit commit ab1afef80fac8e34258ff41fc1b867c702daa24b Reflog: (Scott Chacon Reflog message: updating HEAD Author: Scott Chacon Date: Fri May 22 18:15: modified repo.rb a bit 看 起 来 下 面 的 那 个 就 是 你 丢 失 的 提 交, 你 可 以 通 过 创 建 一 个 新 的 分 支 指 向 这 个 提 交 来 恢 复 它 例 如, 你 可 以 创 建 一 个 名 为 recover-branch 的 分 支 指 向 这 个 提 交 (ab1afef): $ git branch recover-branch ab1afef $ git log --pretty=oneline recover-branch ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit 484a e19aadb7c cfcdf19a added repo.rb 1a410efbd13591db ebc7a059dd55cfe9 third commit cac0cab538b970a37ea1e769cbbde608743bc96d second commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit 不 错, 现 在 有 一 个 名 为 recover-branch 的 分 支 是 你 的 master 分 支 曾 经 指 向 的 地 方, 再 一 次 使 得 前 两 次 提 交 可 到 达 了 接 下 来, 假 设 你 丢 失 的 提 交 因 为 某 些 原 因 不 在 引 用 日 志 中 - 我 们 可 以 通 过 移 除 recover-branch 分 支 并 删 除 引 用 日 志 来 模 拟 这 种 情 况 现 在 前 两 次 提 交 又 不 被 任 何 分 支 指 向 了 : $ git branch -D recover-branch $ rm -Rf.git/logs/ 由 于 引 用 日 志 数 据 存 放 在.git/logs/ 目 录 中, 现 在 你 已 经 没 有 引 用 日 志 了 这 时 该 如 何 恢 复 那 次 提 交? 一 种 方 式 是 使 用 git fsck 实 用 工 具, 将 会 检 查 数 据 库 的 完 整 性 如 果 使 用 一 个 --full 选 项 运 行 它, 它 会 向 你 显 示 出 所 有 没 有 被 其 他 对 象 指 向 的 对 象 :

451 $ git fsck --full Checking object directories: 100% (256/256), done. Checking objects: 100% (18/18), done. dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4 dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9 dangling blob 7108f7ecb345ee9d f147cdad4d 在 这 个 例 子 中, 你 可 以 在 dangling commit 后 看 到 你 丢 失 的 提 交 现 在 你 可 以 用 和 之 前 相 同 的 方 法 恢 复 这 个 提 交, 也 就 是 添 加 一 个 指 向 这 个 提 交 的 分 支 移 除 对 象 Git 有 很 多 很 棒 的 功 能, 但 是 其 中 一 个 特 性 会 导 致 问 题,git clone 会 下 载 整 个 项 目 的 历 史, 包 括 每 一 个 文 件 的 每 一 个 版 本 如 果 所 有 的 东 西 都 是 源 代 码 那 么 这 很 好, 因 为 Git 被 高 度 优 化 来 有 效 地 存 储 这 种 数 据 然 而, 如 果 某 个 人 在 之 前 向 项 目 添 加 了 一 个 大 小 特 别 大 的 文 件, 即 使 你 将 这 个 文 件 从 项 目 中 移 除 了, 每 次 克 隆 还 是 都 要 强 制 的 下 载 这 个 大 文 件 之 所 以 会 产 生 这 个 问 题, 是 因 为 这 个 文 件 在 历 史 中 是 存 在 的, 它 会 永 远 在 那 里 当 你 迁 移 Subversion 或 Perforce 仓 库 到 Git 的 时 候, 这 会 是 一 个 严 重 的 问 题 因 为 这 些 版 本 控 制 系 统 并 不 下 载 所 有 的 历 史 文 件, 所 以 这 种 文 件 所 带 来 的 问 题 比 较 少 如 果 你 从 其 他 的 版 本 控 制 系 统 迁 移 到 Git 时 发 现 仓 库 比 预 期 的 大 得 多, 那 么 你 就 需 要 找 到 并 移 除 这 些 大 文 件 警 告 : 这 个 操 作 对 提 交 历 史 的 修 改 是 破 坏 性 的 它 会 从 你 必 须 修 改 或 移 除 一 个 大 文 件 引 用 最 早 的 树 对 象 开 始 重 写 每 一 次 提 交 如 果 你 在 导 入 仓 库 后, 在 任 何 人 开 始 基 于 这 些 提 交 工 作 前 执 行 这 个 操 作, 那 么 将 不 会 有 任 何 问 题 - 否 则, 你 必 须 通 知 所 有 的 贡 献 者 他 们 需 要 将 他 们 的 成 果 变 基 到 你 的 新 提 交 上 为 了 演 示, 我 们 将 添 加 一 个 大 文 件 到 测 试 仓 库 中, 并 在 下 一 次 提 交 中 删 除 它, 现 在 我 们 需 要 找 到 它, 并 将 它 从 仓 库 中 永 久 删 除 首 先, 添 加 一 个 大 文 件 到 仓 库 中 : $ curl > git.tgz $ git add git.tgz $ git commit -m 'add git tarball' [master 7b30847] add git tarball 1 file changed, 0 insertions(+), 0 deletions(-) create mode git.tgz 哎 呀 - 其 实 这 个 项 目 并 不 需 要 这 个 巨 大 的 压 缩 文 件 现 在 我 们 将 它 移 除 :

452 $ git rm git.tgz rm 'git.tgz' $ git commit -m 'oops - removed large tarball' [master dadf725] oops - removed large tarball 1 file changed, 0 insertions(+), 0 deletions(-) delete mode git.tgz 现 在, 我 们 执 行 gc 来 查 看 数 据 库 占 用 了 多 少 空 间 : $ git gc Counting objects: 17, done. Delta compression using up to 8 threads. Compressing objects: 100% (13/13), done. Writing objects: 100% (17/17), done. Total 17 (delta 1), reused 10 (delta 0) 你 也 可 以 执 行 count-objects 命 令 来 快 速 的 查 看 占 用 空 间 大 小 : $ git count-objects -v count: 7 size: 32 in-pack: 17 packs: 1 size-pack: 4868 prune-packable: 0 garbage: 0 size-garbage: 0 size-pack 的 数 值 指 的 是 你 的 包 文 件 以 KB 为 单 位 计 算 的 大 小, 所 以 你 大 约 占 用 了 5MB 的 空 间 在 最 后 一 次 提 交 前, 使 用 了 不 到 2KB - 显 然, 从 之 前 的 提 交 中 移 除 文 件 并 不 能 从 历 史 中 移 除 它 每 一 次 有 人 克 隆 这 个 仓 库 时, 他 们 将 必 须 克 隆 所 有 的 5MB 来 获 得 这 个 微 型 项 目, 只 因 为 你 意 外 地 添 加 了 一 个 大 文 件 现 在 来 让 我 们 彻 底 的 移 除 这 个 文 件 首 先 你 必 须 找 到 它 在 本 例 中, 你 已 经 知 道 是 哪 个 文 件 了 但 是 假 设 你 不 知 道 ; 该 如 何 找 出 哪 个 文 件 或 哪 些 文 件 占 用 了 如 此 多 的 空 间? 如 果 你 执 行 git gc 命 令, 所 有 的 对 象 将 被 放 入 一 个 包 文 件 中, 你 可 以 通 过 运 行 git verify-pack 命 令, 然 后 对 输 出 内 容 的 第 三 列 ( 即 文 件 大 小 ) 进 行 排 序, 从 而 找 出 这 个 大 文 件 你 也 可 以 将 这 个 命 令 的 执 行 结 果 通 过 管 道 传 送 给 tail 命 令, 因 为 你 只 需 要 找 到 列 在 最 后 的 几 个 大 对 象

453 $ git verify-pack -v.git/objects/pack/pack idx \ sort -k 3 -n \ tail -3 dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob c99a3e86bb1267b236a4b6eff7868d97489af1 blob 你 可 以 看 到 这 个 大 对 象 出 现 在 返 回 结 果 的 最 底 部 : 占 用 5MB 空 间 为 了 找 出 具 体 是 哪 个 文 件, 可 以 使 用 revlist 命 令, 我 们 在 指 定 特 殊 的 提 交 信 息 格 式 中 曾 提 到 过 如 果 你 传 递 --objects 参 数 给 rev-list 命 令, 它 就 会 列 出 所 有 提 交 的 SHA-1 数 据 对 象 的 SHA-1 和 与 它 们 相 关 联 的 文 件 路 径 可 以 使 用 以 下 命 令 来 找 出 你 的 数 据 对 象 的 名 字 : $ git rev-list --objects --all grep 82c99a3 82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz 现 在, 你 只 需 要 从 过 去 所 有 的 树 中 移 除 这 个 文 件 使 用 以 下 命 令 可 以 轻 松 地 查 看 哪 些 提 交 对 这 个 文 件 产 生 改 动 : $ git log --oneline --branches -- git.tgz dadf725 oops - removed large tarball 7b30847 add git tarball 现 在, 你 必 须 重 写 7b30847 提 交 之 后 的 所 有 提 交 来 从 Git 历 史 中 完 全 移 除 这 个 文 件 为 了 执 行 这 个 操 作, 我 们 要 使 用 filter-branch 命 令, 这 个 命 令 在 重 写 历 史 中 也 使 用 过 : $ git filter-branch --index-filter \ 'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^.. Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz' Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2) Ref 'refs/heads/master' was rewritten --index-filter 选 项 类 似 于 在 重 写 历 史 中 提 到 的 的 --tree-filter 选 项, 不 过 这 个 选 项 并 不 会 让 命 令 将 修 改 在 硬 盘 上 检 出 的 文 件, 而 只 是 修 改 在 暂 存 区 或 索 引 中 的 文 件 你 必 须 使 用 git rm --cached 命 令 来 移 除 文 件, 而 不 是 通 过 类 似 rm file 的 命 令 - 因 为 你 需 要 从 索 引 中 移 除 它, 而 不 是 磁 盘 中 还 有 一 个 原 因 是 速 度 - Git 在 运 行 过 滤 器 时, 并 不 会 检 出 每 个 修 订 版 本 到 磁 盘 中, 所 以 这 个 过 程 会 非 常 快 如 果 愿 意 的 话, 你 也 可 以 通 过 --tree-filter 选 项 来 完 成 同 样 的 任 务 git rm 命 令 的 --ignore-unmatch 选 项 告 诉 命 令 : 如 果 尝 试 删 除 的 模 式 不 存 在 时, 不 提 示 错 误 最 后, 使 用 filterbranch 选 项 来 重 写 自 7b30847 提 交 以 来 的 历 史, 也 就 是 这 个 问 题 产 生 的 地 方 否 则, 这 个 命 令 会 从 最 旧 的 提 交 开 始, 这 将 会 花 费 许 多 不 必 要 的 时 间 你 的 历 史 中 将 不 再 包 含 对 那 个 文 件 的 引 用 不 过, 你 的 引 用 日 志 和 你 在.git/refs/original 通 过 filter-

454 branch 选 项 添 加 的 新 引 用 中 还 存 有 对 这 个 文 件 的 引 用, 所 以 你 必 须 移 除 它 们 然 后 重 新 打 包 数 据 库 在 重 新 打 包 前 需 要 移 除 任 何 包 含 指 向 那 些 旧 提 交 的 指 针 的 文 件 : $ rm -Rf.git/refs/original $ rm -Rf.git/logs/ $ git gc Counting objects: 15, done. Delta compression using up to 8 threads. Compressing objects: 100% (11/11), done. Writing objects: 100% (15/15), done. Total 15 (delta 1), reused 12 (delta 0) 让 我 们 看 看 你 省 了 多 少 空 间 $ git count-objects -v count: 11 size: 4904 in-pack: 15 packs: 1 size-pack: 8 prune-packable: 0 garbage: 0 size-garbage: 0 打 包 的 仓 库 大 小 下 降 到 了 8K, 比 5MB 好 很 多 可 以 从 size 的 值 看 出, 这 个 大 文 件 还 在 你 的 松 散 对 象 中, 并 没 有 消 失 ; 但 是 它 不 会 在 推 送 或 接 下 来 的 克 隆 中 出 现, 这 才 是 最 重 要 的 如 果 真 的 想 要 删 除 它, 可 以 通 过 有 --expire 选 项 的 git prune 命 令 来 完 全 地 移 除 那 个 对 象 : $ git prune --expire now $ git count-objects -v count: 0 size: 0 in-pack: 15 packs: 1 size-pack: 8 prune-packable: 0 garbage: 0 size-garbage: 0 环 境 变 量 Git 总 是 在 一 个 bash shell 中 运 行, 并 借 助 一 些 shell 环 境 变 量 来 决 定 它 的 运 行 方 式 有 时 候, 知 道 它 们 是 什 么

455 以 及 它 们 如 何 让 Git 按 照 你 想 要 的 方 式 去 运 行 会 很 有 用 这 里 不 会 列 出 所 有 的 Git 环 境 变 量, 但 我 们 会 涉 及 最 有 的 那 部 分 全 局 行 为 像 通 常 的 程 序 一 样,Git 的 常 规 行 为 依 赖 于 环 境 变 量 GIT_EXEC_PATH 决 定 Git 到 哪 找 它 的 子 程 序 ( 像 git-commit, git-diff 等 等 ) 你 可 以 用 git --exec -path 来 查 看 当 前 设 置. 通 常 不 会 考 虑 修 改 HOME 这 个 变 量 ( 太 多 其 它 东 西 都 依 赖 它 ), 这 是 Git 查 找 全 局 配 置 文 件 的 地 方 如 果 你 想 要 一 个 包 括 全 局 配 置 的 真 正 的 便 携 版 Git, 你 可 以 在 便 携 版 Git 的 shell 配 置 中 覆 盖 HOME 设 置 PREFIX 也 类 似, 除 了 用 于 系 统 级 别 的 配 置 Git 在 $PREFIX/etc/gitconfig 查 找 此 文 件. 如 果 设 置 了 GIT_CONFIG_NOSYSTEM, 就 禁 用 系 统 级 别 的 配 置 文 件 这 在 系 统 配 置 影 响 了 你 的 命 令, 而 你 又 无 权 限 修 改 的 时 候 很 有 用 GIT_PAGER 控 制 在 命 令 行 上 显 示 多 页 输 出 的 程 序 如 果 这 个 没 有 设 置, 就 会 用 PAGER. GIT_EDITOR 当 用 户 需 要 编 辑 一 些 文 本 ( 比 如 提 交 信 息 ) 时, Git 会 启 动 这 个 编 辑 器 如 果 没 设 置, 就 会 用 EDITOR 版 本 库 位 置 Git 用 了 几 个 变 量 来 确 定 它 如 何 与 当 前 版 本 库 交 互 GIT_DIR 是.git 目 录 的 位 置. 如 果 这 个 没 有 设 置, Git 会 按 照 目 录 树 逐 层 向 上 查 找.git 目 录, 直 到 到 达 ~ 或 / GIT_CEILING_DIRECTORIES 控 制 查 找.git 目 录 的 行 为 如 果 你 访 问 加 载 很 慢 的 目 录 ( 如 那 些 磁 带 机 上 的 或 通 过 网 络 连 接 访 问 的 ), 你 可 能 会 想 让 Git 早 点 停 止 尝 试, 尤 其 是 shell 构 建 时 调 用 了 Git GIT_WORK_TREE 是 非 空 版 本 库 的 工 作 目 录 的 根 路 径 如 果 没 指 定, 就 使 用 $GIT_DIR 的 父 目 录 GIT_INDEX_FILE 是 索 引 文 件 的 路 径 ( 只 有 非 空 版 本 库 有 ) GIT_OBJECT_DIRECTORY 用 来 指 定.git/objects 目 录 的 位 置 GIT_ALTERNATE_OBJECT_DIRECTORIES 一 个 冒 号 分 割 的 列 表 ( 格 式 类 似 /dir/one:/dir/two: ) 用 来 告 诉 Git 到 哪 里 去 找 不 在 GIT_OBJECT_DIRECTORY 目 录 中 的 对 象. 如 果 你 有 很 多 项 目 有 相 同 内 容 的 大 文 件, 这 个 可 以 用 来 避 免 存 储 过 多 备 份

456 路 径 规 则 所 谓 pathspec 是 指 你 在 Git 中 如 何 指 定 路 径, 包 括 通 配 符 的 使 用 它 们 会 在.gitignore 文 件 中 用 到, 命 令 行 里 也 会 用 到 (git add *.c) GIT_GLOB_PATHSPECS and GIT_NOGLOB_PATHSPECS 控 制 通 配 符 在 路 径 规 则 中 的 默 认 行 为 如 果 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 用 这 些 环 境 变 量 作 主 要 的 信 息 源 仅 当 这 些 值 不 存 在 才 回 退 到 预 置 的 值 GIT_AUTHOR_NAME 是 author 字 段 的 可 读 的 名 字 GIT_AUTHOR_ 是 author 字 段 的 邮 件 GIT_AUTHOR_DATE 是 author 字 段 的 时 间 戳 GIT_COMMITTER_NAME 是 committer 字 段 的 可 读 的 名 字 GIT_COMMITTER_ 是 committer 字 段 的 邮 件 GIT_COMMITTER_DATE 是 committer 字 段 的 时 间 戳 如 果 user. 没 有 配 置, 就 会 用 到 指 定 的 邮 件 地 址 如 果 这 个 也 没 有 设 置, Git 继 续 回 退 使 用 系 统 用 户 和 主 机 名 网 络 Git 使 用 curl 库 通 过 HTTP 来 完 成 网 络 操 作, 所 以 GIT_CURL_VERBOSE 告 诉 Git 显 示 所 有 由 那 个 库 产 生 的 消 息 这 跟 在 命 令 行 执 行 curl -v 差 不 多 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 配 置 的 值

457 GIT_HTTP_USER_AGENT 设 置 Git 在 通 过 HTTP 通 讯 时 用 到 的 user-agent 默 认 值 类 似 于 git/2.0.0 比 较 和 合 并 GIT_DIFF_OPTS 这 个 有 点 起 错 名 字 了 有 效 值 仅 支 持 -u<n> 或 --unified=<n>, 用 来 控 制 在 git diff 命 令 中 显 示 的 内 容 行 数 GIT_EXTERNAL_DIFF 用 来 覆 盖 diff.external 配 置 的 值 如 果 设 置 了 这 个 值, 当 执 行 Git git diff 时,Git 会 调 用 该 程 序 GIT_DIFF_PATH_COUNTER 和 GIT_DIFF_PATH_TOTAL 对 于 GIT_EXTERNAL_DIFF 或 diff.external 指 定 的 程 序 有 用 前 者 表 示 在 一 系 列 文 件 中 哪 个 是 被 比 较 的 ( 从 1 开 始 ), 后 者 表 示 每 批 文 件 的 总 数 GIT_MERGE_VERBOSITY 控 制 递 归 合 并 策 略 的 输 出 允 许 的 值 有 下 面 这 些 : 0 什 么 都 不 输 出, 除 了 可 能 会 有 一 个 错 误 信 息 1 只 显 示 冲 突 2 还 显 示 文 件 改 变 3 显 示 因 为 没 有 改 变 被 跳 过 的 文 件 4 显 示 处 理 的 所 有 路 径 5 显 示 详 细 的 调 试 信 息 默 认 值 是 2. 调 试 想 真 正 地 知 道 Git 正 在 做 什 么?Git 内 置 了 相 当 完 整 的 跟 踪 信 息, 你 需 要 做 的 就 是 把 它 们 打 开 这 些 变 量 的 可 以 用 的 值 如 下 : true, 1, 或 2 跟 踪 类 别 写 到 标 准 错 误 输 出. 以 / 开 头 的 绝 对 路 径 跟 踪 输 出 会 被 写 到 那 个 文 件 GIT_TRACE 控 制 常 规 跟 踪, 它 并 不 适 用 于 特 殊 情 况 它 跟 踪 的 范 围 包 括 别 名 的 展 开 和 其 他 子 程 序 的 委 托

458 $ GIT_TRACE=true git lga 20:12: git.c:554 trace: exec: 'git-lga' 20:12: run-command.c:341 trace: run_command: 'git-lga' 20:12: git.c:282 trace: alias expansion: lga => 'log' '--graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all' 20:12: git.c:349 trace: built-in: git 'log' '-- graph' '--pretty=oneline' '--abbrev-commit' '--decorate' '--all' 20:12: run-command.c:341 trace: run_command: 'less' 20:12: run-command.c:192 trace: exec: 'less' GIT_TRACE_PACK_ACCESS 控 制 访 问 打 包 文 件 的 跟 踪 信 息 第 一 个 字 段 是 被 访 问 的 打 包 文 件, 第 二 个 是 文 件 的 偏 移 量 : $ GIT_TRACE_PACK_ACCESS=true git status 20:10: sha1_file.c:2088.git/objects/pack/packc3fa...291e.pack 12 20:10: sha1_file.c:2088.git/objects/pack/packc3fa...291e.pack :10: sha1_file.c:2088.git/objects/pack/packc3fa...291e.pack # [ ] 20:10: sha1_file.c:2088.git/objects/pack/packe80e...e3d2.pack :10: sha1_file.c:2088.git/objects/pack/packe80e...e3d2.pack On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean GIT_TRACE_PACKET 打 开 网 络 操 作 包 级 别 的 跟 踪 信 息

459 $ GIT_TRACE_PACKET=true git ls-remote origin 20:15: pkt-line.c:46 packet: git< # service=git-upload-pack 20:15: pkt-line.c:46 packet: git< :15: pkt-line.c:46 packet: git< 97b8860c071898d9e162678ea1035a8ced2f8b1f HEAD\0multi_ack thin-pack sideband side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=head:refs/heads/master agent=git/ :15: pkt-line.c:46 packet: git< 0f20ae29889d61f2e93ae00fd34f1cdb refs/heads/ab/add-interactiveshow-diff-func-name 20:15: pkt-line.c:46 packet: git< 36dc827bc9d17f80ed4f326de21247a5d1341fbc refs/heads/ah/doc-gitk-config # [ ] GIT_TRACE_PERFORMANCE 控 制 性 能 数 据 的 日 志 打 印 输 出 显 示 了 每 个 Git 命 令 调 用 花 费 的 时 间 $ GIT_TRACE_PERFORMANCE=true git gc 20:18: trace.c:414 performance: s: git command: 'git' 'pack-refs' '--all' '--prune' 20:18: trace.c:414 performance: s: git command: 'git' 'reflog' 'expire' '--all' Counting objects: , done. Delta compression using up to 8 threads. Compressing objects: 100% (43413/43413), done. Writing objects: 100% (170994/170994), done. Total (delta ), reused (delta ) 20:18: trace.c:414 performance: s: git command: 'git' 'pack-objects' '--keep-true-parents' '--honor-pack-keep' '--non-empty' '--all' '--reflog' '--unpack-unreachable=2.weeks.ago' '-- local' '--delta-base-offset' '.git/objects/pack/.tmp pack' 20:18: trace.c:414 performance: s: git command: 'git' 'prune-packed' 20:18: trace.c:414 performance: s: git command: 'git' 'update-server-info' 20:18: trace.c:414 performance: s: git command: 'git' 'repack' '-d' '-l' '-A' '--unpack-unreachable=2.weeks.ago' Checking connectivity: , done. 20:18: trace.c:414 performance: s: git command: 'git' 'prune' '--expire' '2.weeks.ago' 20:18: trace.c:414 performance: s: git command: 'git' 'rerere' 'gc' 20:18: trace.c:414 performance: s: git command: 'git' 'gc'

460 GIT_TRACE_SETUP 显 示 Git 发 现 的 关 于 版 本 库 和 交 互 环 境 的 信 息 $ GIT_TRACE_SETUP=true git status 20:19: trace.c:315 setup: git_dir:.git 20:19: trace.c:316 setup: worktree: /Users/ben/src/git 20:19: trace.c:317 setup: cwd: /Users/ben/src/git 20:19: trace.c:318 setup: prefix: (null) On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean 其 它 如 果 指 定 了 GIT_SSH, Git 连 接 SSH 主 机 时 会 用 指 定 的 程 序 代 替 ssh 它 会 被 用 $GIT_SSH [username@]host [-p <port>] <command> 的 命 令 方 式 调 用 这 不 是 配 置 定 制 ssh 调 用 方 式 的 最 简 单 的 方 法 ; 它 不 支 持 额 外 的 命 令 行 参 数, 所 以 你 必 须 写 一 个 封 装 脚 本 然 后 让 GIT_SSH 指 向 它 可 能 用 ~/.ssh/config 会 更 简 单 GIT_ASKPASS 覆 盖 了 core.askpass 配 置 这 是 Git 需 要 向 用 户 请 求 验 证 时 用 到 的 程 序, 它 接 受 一 个 文 本 提 示 作 为 命 令 行 参 数, 并 在 stdout 中 返 回 应 答 ( 查 看 凭 证 存 储 _ 访 问 更 多 相 关 内 容 ) GIT_NAMESPACE 控 制 有 命 令 空 间 的 引 用 的 访 问, 与 --namespace 标 志 是 相 同 的. 这 主 要 在 服 务 器 端 有 用, 如 果 你 想 在 一 个 版 本 库 中 存 储 单 个 版 本 库 的 多 个 fork, 只 要 保 持 引 用 是 隔 离 的 就 可 以 GIT_FLUSH 强 制 Git 在 向 标 准 输 出 增 量 写 入 时 使 用 没 有 缓 存 的 I/O 设 置 为 1 让 Git 刷 新 更 多, 设 置 为 0 则 使 所 有 的 输 出 被 缓 存 默 认 值 ( 若 此 变 量 未 设 置 ) 是 根 据 活 动 和 输 出 模 式 的 不 同 选 择 合 适 的 缓 存 方 案 GIT_REFLOG_ACTION 让 你 可 以 指 定 描 述 性 的 文 字 写 到 reflog 中 这 有 个 例 子 : $ GIT_REFLOG_ACTION="my action" git commit --allow-empty -m 'my message' [master 9e3d55a] my message $ git reflog -1 9e3d55a HEAD@{0}: my action: my message 总 结 现 在, 你 应 该 相 当 了 解 Git 在 背 后 都 做 了 些 什 么 工 作, 并 且 在 一 定 程 度 上 也 知 道 了 Git 是 如 何 实 现 的 本 章 讨 论 了 很 多 底 层 命 令, 这 些 命 令 比 我 们 在 本 书 其 余 部 分 学 到 的 高 层 命 令 来 得 更 原 始, 也 更 简 洁 从 底 层 了 解 Git 的 工 作 原 理 有 助 于 更 好 地 理 解 Git 在 内 部 是 如 何 运 作 的, 也 方 便 你 能 够 针 对 特 定 的 工 作 流 写 出 自 己 的 工 具 和 脚 本 作 为 一 套 内 容 寻 址 文 件 系 统,Git 不 仅 仅 是 一 个 版 本 控 制 系 统, 它 同 时 是 一 个 非 常 强 大 且 易 用 的 工 具 我 们 希 望 你 可 以 借 助 新 学 到 的 Git 内 部 原 理 相 关 知 识 来 实 现 出 自 己 的 应 用, 并 且 以 更 高 级 更 得 心 应 手 的 方 式 来 驾 驭

461 Git

462 Appendix A: 其 它 环 境 中 的 Git 从 头 至 尾 读 到 了 这 里, 你 肯 定 已 经 掌 握 了 不 少 使 用 Git 命 令 行 操 作 的 知 识 你 学 会 了 操 作 本 地 文 件, 通 过 网 络 连 接 你 的 仓 库, 以 及 与 他 人 进 行 有 效 率 的 合 作 但 是 故 事 并 未 就 此 结 束 ;Git 通 常 只 是 更 大 的 生 态 圈 的 一 部 分, 在 某 些 情 况 下 使 用 终 端 并 不 是 最 合 适 的 方 式 现 在 就 让 我 们 来 了 解 一 下 如 何 在 其 它 类 型 的 环 境 中 更 好 地 使 用 Git, 以 及 别 的 应 用 ( 包 括 你 的 ) 如 何 与 Git 进 行 协 作 图 形 界 面 Git 的 原 生 环 境 是 终 端 在 那 里, 你 可 以 体 验 到 最 新 的 功 能, 也 只 有 在 那 里, 你 才 能 尽 情 发 挥 Git 的 全 部 能 力 但 是 对 于 某 些 任 务 而 言, 纯 文 本 并 不 是 最 佳 的 选 择 ; 有 时 候 你 确 实 需 要 一 个 可 视 化 的 展 示 方 式, 而 且 有 些 用 户 更 习 惯 那 种 能 点 击 的 界 面 有 一 点 请 注 意, 不 同 的 界 面 是 为 不 同 的 工 作 流 程 设 计 的 一 些 客 户 端 的 作 者 为 了 支 持 某 种 他 认 为 高 效 的 工 作 流 程, 经 过 精 心 挑 选, 只 显 示 了 Git 功 能 的 一 个 子 集 每 种 工 具 都 有 其 特 定 的 目 的 和 意 义, 从 这 个 角 度 来 看, 不 能 说 某 种 工 具 比 其 它 的 ` 更 好 ' 还 有 请 注 意, 没 有 什 么 事 情 是 图 形 界 面 客 户 端 可 以 做 而 命 令 行 客 户 端 不 能 做 的 ; 命 令 行 始 终 是 你 可 以 完 全 操 控 仓 库 并 发 挥 出 全 部 力 量 的 地 方 gitk 和 git-gui 在 安 装 Git 的 同 时, 你 也 装 好 了 它 提 供 的 可 视 化 工 具,gitk 和 git-gui gitk 是 一 个 历 史 记 录 的 图 形 化 查 看 器 你 可 以 把 它 当 作 是 基 于 git log 和 git grep 命 令 的 一 个 强 大 的 图 形 操 作 界 面 当 你 需 要 查 找 过 去 发 生 的 某 次 记 录, 或 是 可 视 化 查 看 项 目 历 史 的 时 候, 你 将 会 用 到 这 个 工 具 使 用 Gitk 的 最 简 单 方 法 就 是 从 命 令 行 打 开 只 需 cd 到 一 个 Git 仓 库, 然 后 键 入 : $ gitk [git log options] Gitk 可 以 接 受 很 多 命 令 行 选 项, 其 中 的 大 部 分 都 直 接 传 给 底 层 的 git log 去 执 行 了 --all 可 能 是 这 其 中 最 有 用 的 一 个, 它 告 诉 gitk 去 尽 可 能 地 从 任 何 引 用 查 找 提 交 并 显 示, 而 不 仅 仅 是 从 HEAD Gitk 的 界 面 看 起 来 长 这 样 :

463 Figure 153. gitk 历 史 查 看 器 这 张 图 看 起 来 就 和 执 行 git log --graph 命 令 的 输 出 差 不 多 ; 每 个 点 代 表 一 次 提 交, 线 代 表 父 子 关 系, 而 彩 色 的 方 块 则 用 来 标 示 一 个 个 引 用 黄 点 表 示 HEAD, 红 点 表 示 尚 未 提 交 的 本 地 变 动 下 方 的 窗 口 用 来 显 示 当 前 选 中 的 提 交 的 具 体 信 息 ; 评 论 和 补 丁 显 示 在 左 侧, 摘 要 显 示 在 右 侧 中 间 则 是 一 组 用 来 搜 索 历 史 的 控 件 与 之 相 比,git-gui 则 主 要 是 一 个 用 来 制 作 提 交 的 工 具 打 开 它 的 最 简 单 方 法 也 是 从 命 令 行 启 动 : $ git gui 它 的 界 面 长 这 个 样 子 :

464 Figure 154. git-gui 提 交 工 具 左 侧 是 索 引 区 ; 未 暂 存 的 修 改 显 示 在 上 方, 已 暂 存 的 修 改 显 示 在 下 方 你 可 以 通 过 点 击 文 件 名 左 侧 的 图 标 来 将 该 文 件 在 暂 存 状 态 与 未 暂 存 状 态 之 间 切 换, 你 也 可 以 通 过 选 中 一 个 文 件 名 来 查 看 它 的 详 情 右 侧 窗 口 的 上 方 以 diff 格 式 来 显 示 当 前 选 中 文 件 发 生 了 变 动 的 地 方 你 可 以 通 过 右 击 某 一 区 块 或 行 从 而 将 这 一 区 块 或 行 放 入 暂 存 区 右 侧 窗 口 的 下 方 是 写 日 志 和 执 行 操 作 的 地 方 在 文 本 框 中 键 入 日 志 然 后 点 击 提 交 就 和 执 行 git commit 的 效 果 差 不 多 如 果 你 想 要 修 订 上 一 次 提 交, 可 以 选 中 ` 修 订 ' 按 钮, 上 次 一 提 交 的 内 容 就 会 显 示 在 暂 存 区 然 后 你 就 可 以 简 单 的 对 修 改 进 行 暂 存 和 取 消 暂 存 操 作, 更 新 提 交 日 志, 然 后 再 次 点 击 提 交 用 这 个 新 的 提 交 来 覆 盖 上 一 次 提 交 gitk 和 git-gui 就 是 针 对 某 种 任 务 设 计 的 工 具 的 两 个 例 子 它 们 分 别 为 了 不 同 的 目 的 ( 即 查 看 历 史 和 制 作 提 交 ) 而 进 行 了 精 简, 略 去 了 用 不 到 的 功 能 Mac 和 Windows 上 的 GitHub 客 户 端 GitHub 发 布 了 两 个 面 向 工 作 流 程 的 Git 客 户 端 :Windows 版, 和 Mac 版 它 们 很 好 的 展 示 了 一 个 面 向 工 作 流 程 的 工 具 应 该 是 什 么 样 子 专 注 于 提 升 那 些 常 用 的 功 能 及 其 协 作 的 可 用 性, 而 不 是 实 现 Git 的 所 有 功 能. 它 们 看 起 来 长 这 个 样 子 :

465 Figure 155. GitHub Mac 客 户 端 Figure 156. GitHub Windows 客 户 端 我 们 在 设 计 的 时 候 就 努 力 将 二 者 的 外 观 和 操 作 体 验 都 保 持 一 致, 因 此 本 章 会 把 他 们 当 做 同 一 个 产 品 来 介 绍 我 们 并 不 会 详 细 地 介 绍 该 工 具 的 每 一 个 功 能 ( 因 为 它 们 本 身 也 有 文 档 ), 但 请 快 速 了 解 一 下 变 更 窗 口 ( 你 大 部 分 时 间 都 会 花 在 使 用 该 窗 口 上 ) 的 以 下 几 点 :

466 左 侧 是 正 在 追 踪 的 仓 库 的 列 表 ; 通 过 点 击 左 上 方 的 + 图 标, 你 可 以 添 加 一 个 需 要 追 踪 的 仓 库 ( 既 可 以 是 通 过 clone, 也 可 以 从 本 地 添 加 ) 中 间 是 输 入 - 提 交 区, 你 可 以 在 这 里 输 入 提 交 日 志, 以 及 选 择 哪 些 文 件 需 要 被 提 交 ( 在 Windows 上, 提 交 历 史 就 显 示 在 这 个 区 域 的 下 方 ; 在 Mac 上, 提 交 历 史 有 一 个 单 独 的 窗 口 ) 右 侧 是 修 改 查 看 区, 它 会 告 诉 你 工 作 目 录 里 哪 些 东 西 被 修 改 了 ( 译 注 : 修 改 模 式 ), 或 选 中 的 提 交 里 包 括 了 哪 些 修 改 ( 译 注 : 历 史 模 式 ) 最 后 需 要 熟 悉 的 是 右 上 角 的 Sync 按 钮, 你 主 要 通 过 这 个 按 钮 来 进 行 网 络 上 的 交 互 NOTE 你 不 需 要 注 册 GitHub 账 号 也 可 以 使 用 这 些 工 具 尽 管 它 们 是 按 照 GitHub 推 荐 的 工 作 流 程 来 设 计 的, 并 突 出 提 升 了 一 些 GitHub 的 服 务 体 验, 但 它 们 可 以 在 任 何 Git 仓 库 上 工 作 良 好, 也 可 以 通 过 网 络 连 接 到 任 意 Git 主 机 安 装 GitHub 的 Windows 客 户 端 可 以 从 下 载,Mac 客 户 端 可 以 从 下 载 第 一 次 打 开 软 件 时, 它 会 引 导 你 进 行 一 系 列 的 首 次 使 用 设 置, 例 如 设 置 你 的 姓 名 和 电 子 邮 件, 它 还 会 智 能 地 帮 你 调 整 一 些 常 用 的 默 认 设 置, 例 如 凭 证 缓 存 和 CRLF 的 处 理 方 式 它 们 都 是 ` 绿 色 软 件 ' 如 果 软 件 打 开 发 现 有 更 新, 下 载 和 安 装 升 级 包 都 是 在 后 台 完 成 的 为 方 便 起 见 它 们 还 打 包 了 一 份 Git, 也 就 是 说 你 一 旦 安 装 好 就 再 也 无 需 劳 心 升 级 的 事 情 了 Windows 的 客 户 端 还 提 供 了 快 捷 方 式, 可 以 启 动 装 了 Posh-git 插 件 的 Powershell, 在 本 章 的 后 面 一 节 我 们 会 详 细 介 绍 这 方 面 的 内 容 接 下 来 我 们 给 它 设 置 一 些 工 作 仓 库 客 户 端 会 显 示 你 在 GitHub 上 有 权 限 操 作 的 仓 库 的 列 表, 你 可 以 选 择 一 个 然 后 一 键 克 隆 如 果 你 本 地 已 经 建 立 了 仓 库, 只 需 要 用 鼠 标 把 它 从 Finder 或 Windows 资 源 管 理 器 拖 进 GitHub 客 户 端 窗 口, 就 可 以 把 该 仓 库 添 加 到 左 侧 的 仓 库 列 表 里 面 去 了 推 荐 的 工 作 流 程 安 装 并 配 置 好 以 后, 你 就 可 以 使 用 GitHub 客 户 端 来 执 行 一 些 常 见 的 Git 任 务 该 工 具 所 推 荐 的 工 作 流 程 有 时 也 被 叫 做 GitHub 流 我 们 在 GitHub 流 程 一 节 中 对 此 有 详 细 的 介 绍, 其 要 点 是 (a) 你 会 提 交 到 一 个 分 支 ;(b) 你 需 要 经 常 与 远 程 仓 库 保 持 同 步 两 个 平 台 上 的 客 户 端 在 分 支 管 理 上 有 所 不 同 在 Mac 上, 创 建 分 支 的 按 钮 在 窗 口 的 上 方 : Figure 157. Mac 上 的 ` 创 建 分 支 ' 按 钮 在 Windows 上, 你 可 以 通 过 在 分 支 切 换 挂 件 中 输 入 新 分 支 的 名 称 来 完 成 创 建 :

467 Figure 158. 在 Windows 上 创 建 分 支 分 支 创 建 好 以 后, 新 建 提 交 就 变 得 非 常 简 单 直 接 了 现 在 工 作 目 录 中 做 一 些 修 改, 然 后 切 换 到 GitHub 客 户 端 窗 口, 你 所 做 的 修 改 就 会 显 示 在 那 里 输 入 提 交 日 志, 选 中 那 些 需 要 被 包 含 在 本 次 提 交 中 的 文 件, 然 后 点 击 提 交 按 钮 ( 也 可 以 在 键 盘 上 按 ctrl-enter 或 -enter) 同 步 功 能 是 你 在 网 络 上 和 其 它 仓 库 交 互 的 主 要 途 径 push,fetch,merge, 和 rebase 在 Git 内 部 是 一 连 串 独 立 的 操 作, 而 GitHub 客 户 端 将 这 些 操 作 都 合 并 成 了 单 独 一 个 功 能 你 点 击 同 步 按 钮 时 实 际 上 会 发 生 如 下 这 些 操 作 : 1. git pull --rebase 如 果 上 述 命 令 由 于 存 在 合 并 冲 突 而 失 败, 则 会 退 而 执 行 git pull --no -rebase 2. git push 如 果 你 遵 循 推 荐 的 工 作 流 程, 以 上 就 是 最 常 用 的 一 系 列 命 令, 因 此 将 它 们 合 并 为 一 个 让 事 情 简 单 了 很 多 小 结 这 些 工 具 是 为 其 各 自 针 对 的 工 作 流 程 所 量 身 定 做 的 开 发 者 和 非 开 发 者 可 以 轻 松 地 在 分 分 钟 内 就 搭 建 起 项 目 协 作 环 境, 它 们 还 内 置 了 其 它 辅 助 最 佳 实 践 的 功 能 但 是, 如 果 你 的 工 作 流 程 有 所 不 同, 或 者 你 需 要 在 进 行 网 络 操 作 时 有 更 多 的 控 制, 那 么 建 议 你 考 虑 一 下 其 它 客 户 端 或 者 使 用 命 令 行 其 它 图 形 界 面 除 此 之 外, 还 有 许 许 多 多 其 它 的 图 形 化 Git 客 户 端, 其 中 既 有 单 一 功 能 的 定 制 工 具, 也 有 试 图 提 供 Git 所 有 功 能 的 复 杂 应 用 Git 的 官 方 网 站 整 理 了 一 份 时 下 最 流 行 的 客 户 端 的 清 单 在 Git 的 维 基 站 点 还 可 以 看 到 一 份 更 全 的 清 单 Visual Studio 中 的 Git 从 Visual Studio 2013 Update 1 版 本 开 始,Visual Studio 用 户 可 以 在 他 们 的 IDE 中 直 接 使 用 内 嵌 的 Git 客 户 端 Visual Studio 集 成 源 代 码 版 本 控 制 特 性 已 经 有 很 长 一 段 时 间, 但 面 向 的 是 集 中 式 文 件 锁 定 方 式 的 系 统,Git 并 不 能 很 好 地 符 合 这 种 工 作 流 程 Visual Studio 2013 中 已 经 支 持 Git, 并 独 立 于 原 有 版 本 管 理 系 统, 这 使 得 Visual Studio 和 Git 能 更 好 地 相 互 适 应 想 要 找 到 这 个 特 性, 在 Visual Studio 中 打 开 一 个 已 经 用 Git 管 理 的 项 目 ( 或 者 直 接 在 项 目 目 录 中 git init

468 ), 选 择 菜 单 View > Team Explorer 你 将 看 到 "Connect" 视 图, 大 概 如 下 图 所 示 : Figure 159. 从 Team Explorer 中 连 接 Git 仓 库 Visual Studio 能 够 记 住 所 有 你 打 开 过 的 用 Git 管 理 的 项 目, 它 们 都 在 下 方 的 列 表 中 如 果 没 看 到 你 想 要 的 项 目, 点 击 "Add" 按 钮, 添 加 项 目 工 作 目 录 的 路 径 双 击 其 中 一 个 本 地 的 Git 仓 库 会 将 你 带 入 "Home" 视 图, 大 概 如 Visual Studio 中 的 Git 仓 库 的 Home 视 图 所 示 这 是 一 个 执 行 Git 操 作 的 操 作 中 心 ; 当 你 正 在 编 写 代 码 的 时 候, 你 可 能 主 要 关 注 "Changes" 视 图, 当 需 要 拉 取 同 伴 的 改 动 时, 你 将 使 用 "Unsynced Commits" 和 "Branches" 视 图 Figure 160. Visual Studio 中 的 Git 仓 库 的 Home 视 图 Visual Studio 现 在 拥 有 一 套 着 眼 于 任 务 的 强 大 Git 操 作 界 面 它 包 括 线 性 的 历 史 视 图 diff 视 图 远 程 仓 库 操 作 命 令, 以 及 其 它 很 多 功 能 这 个 特 性 的 完 整 文 档 ( 放 在 这 里 并 不 合 适 ) 请 参 阅

469 Eclipse 中 的 Git Eclipse 附 带 了 一 个 名 为 Egit 的 插 件, 它 提 供 了 一 个 非 常 完 善 的 Git 操 作 接 口 这 个 插 件 可 以 通 过 切 换 到 Git 视 图 来 使 用 :(Window > Open Perspective > Other, 然 后 选 择 Git ) Figure 161. Eclipse 中 EGit 的 界 面 环 境 EGit 提 供 了 许 多 强 大 的 帮 助 文 档, 你 能 通 过 下 面 的 操 作 来 访 问 它 : 单 击 菜 单 Help > Help Contents, 然 后 从 内 容 列 表 中 选 择 EGit Documentation 节 点 Bash 中 的 Git 如 果 你 是 一 名 Bash 用 户, 你 可 以 从 中 发 掘 出 一 些 Shell 的 特 性, 让 你 在 使 用 Git 时 更 加 随 心 所 欲 实 际 上 Git 附 带 了 几 个 Shell 的 插 件, 但 是 这 些 插 件 并 不 是 默 认 打 开 的 首 先, 你 需 要 从 Git 源 代 码 中 获 得 一 份 contrib/completion/git-completion.bash 文 件 的 拷 贝 将 这 个 文 件 复 制 到 一 个 相 对 便 捷 的 目 录, 例 如 你 的 Home 目 录, 并 且 将 它 的 路 径 添 加 到.bashrc 中 :. ~/git-completion.bash 做 完 这 些 之 后, 请 将 你 当 前 的 目 录 切 换 到 某 一 个 Git 仓 库, 并 且 输 入 :

470 $ git chec<tab> 此 时 Bash 将 会 把 上 面 的 命 令 自 动 补 全 为 git checkout 在 适 当 的 情 况 下, 这 项 功 能 适 用 于 Git 所 有 的 子 命 令 命 令 行 参 数 以 及 远 程 仓 库 与 引 用 名 这 项 功 能 也 可 以 用 于 你 自 己 定 义 的 提 示 符 (prompt), 显 示 当 前 目 录 下 Git 仓 库 的 信 息 根 据 你 的 需 要, 这 个 信 息 可 以 简 单 或 复 杂, 这 里 通 常 有 大 多 数 人 想 要 的 几 个 关 键 信 息, 比 如 当 前 分 支 信 息 和 当 前 工 作 目 录 的 状 态 信 息 要 添 加 你 自 己 的 提 示 符 (prompt), 只 需 从 Git 源 版 本 库 复 制 contrib/completion/git-prompt.sh 文 件 到 你 的 Home 目 录 ( 或 其 他 便 于 你 访 问 与 管 理 的 目 录 ), 并 在.bashrc 里 添 加 这 个 文 件 路 径, 类 似 于 下 面 这 样 :. ~/git-prompt.sh export GIT_PS1_SHOWDIRTYSTATE=1 export PS1='\w$( git_ps1 " (%s)")\$ ' \w 表 示 打 印 当 前 工 作 目 录,\$ 打 印 $ 部 分 的 提 示 符 (prompt), git_ps1 " (%s)" 表 示 通 过 格 式 化 参 数 符 (%s) 调 用 `git-prompt.sh` 脚 本 中 提 供 的 函 数 因 为 有 了 这 个 自 定 义 提 示 符, 现 在 你 的 Bash 提 示 符 (prompt) 在 Git 仓 库 的 任 何 子 目 录 中 都 将 显 示 成 这 样 : Figure 162. 自 定 义 的 bash 提 示 符 (prompt). 这 两 个 脚 本 都 提 供 了 很 有 帮 助 的 文 档 ; 浏 览 git-completion.bash 和 git-prompt.sh 的 内 容 以 获 得 更 多 信 息 Zsh 中 的 Git Git 还 为 Zsh 提 供 了 一 个 Tab 补 全 库 复 制 contrib/completion/git-completion.zsh 到 你 的 home 目 录, 然 后 在.zshrc 中 source 即 可 相 对 于 Bash,Zsh 的 接 口 更 加 强 大 :

471 $ git che<tab> check-attr -- 显 示 gitattributes 信 息 check-ref-format -- 检 查 引 用 名 称 是 否 符 合 规 范 checkout -- 从 工 作 区 中 检 出 分 支 或 路 径 checkout-index -- 从 暂 存 区 拷 贝 文 件 至 工 作 目 录 cherry -- 查 找 没 有 被 合 并 至 上 游 的 提 交 cherry-pick -- 从 一 些 已 存 在 的 提 交 中 应 用 更 改 意 义 不 明 的 Tab 补 全 并 不 仅 仅 会 被 列 出 ; 它 们 还 会 有 帮 助 性 的 描 述, 你 可 以 通 过 不 断 敲 击 Tab 以 图 形 方 式 浏 览 补 全 列 表 该 功 能 可 用 于 Git 命 令 它 们 的 参 数 和 在 仓 库 中 内 容 的 名 称 ( 例 如 refs 和 remotes), 还 有 文 件 名 和 其 他 所 有 Zsh 知 道 如 何 去 补 全 的 项 目 在 提 示 符 自 定 义 方 面,Zsh 很 好 地 兼 容 了 Bash, 并 允 许 你 同 时 使 用 一 个 右 侧 提 示 符 把 如 下 代 码 添 加 至 你 的 ~/.zshrc 文 件 中, 就 可 以 在 右 侧 显 示 分 支 名 称 : setopt prompt_subst. ~/git-prompt.sh export RPROMPT=$'$( git_ps1 "%s")' 当 你 的 命 令 行 位 于 一 个 Git 仓 库 目 录 时, 在 任 何 时 候, 都 可 以 在 命 令 行 窗 口 右 侧 显 示 当 前 分 支 它 看 起 来 像 这 样 :

472 Figure 163. 自 定 义 zsh 提 示 符. Zsh 本 身 已 足 够 强 大, 但 还 有 一 些 专 门 为 它 打 造 的 完 整 框 架, 使 它 更 加 完 善 其 中 之 一 名 为 "oh-my-zsh", 你 可 以 在 找 到 它 oh-my-zsh 的 扩 展 系 统 包 含 强 大 的 Git Tab 补 全 功 能, 且 许 多 提 示 符 " 主 题 " 可 以 展 示 版 本 控 制 数 据 一 个 oh-my-zsh 主 题 的 示 例. 只 是 可 以 其 中 一 个 可 以 通 过 该 系 统 实 现 的 例 子 Figure 164. 一 个 oh-my-zsh 主 题 的 示 例. Powershell 中 的 Git Windows 中 的 普 通 命 令 行 终 端 (cmd.exe) 无 法 自 定 义 Git 使 用 体 验, 但 是 如 果 你 正 在 使 用 Powershell, 那 么 你 就 十 分 幸 运 了 一 个 名 为 Posh-Git ( 的 扩 展 包 提 供 了 强 大 的 tab 补 全 功

473 能, 并 针 对 提 示 符 进 行 了 增 强, 以 帮 助 你 聚 焦 于 你 的 仓 库 状 态 它 看 起 来 像 : Figure 165. 附 带 了 Posh-Git 扩 展 包 的 Powershell 如 果 你 已 经 在 Windows 上 安 装 了 GitHub,Posh-Git 也 会 被 安 装, 你 只 需 要 添 加 以 下 两 行 到 你 的 profile.ps1 文 件 ( 文 件 位 于 C:\Users\<username>\Documents\WindowsPowerShell):. (Resolve-Path "$env:localappdata\github\shell.ps1"). $env:github_posh_git\profile.example.ps1 如 果 你 没 有 在 Windows 上 安 装 GitHub, 只 需 要 从 ( 下 载 一 份 Posh-Git 发 行 版, 并 且 解 压 至 WindowsPowershell 目 录 然 后 以 管 理 员 权 限 打 开 Powershell 提 示 符, 并 且 执 行 下 面 的 命 令 : > Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Confirm > cd ~\Documents\WindowsPowerShell\posh-git >.\install.ps1 它 将 会 向 你 的 profile.ps1 文 件 添 加 适 当 的 内 容,Posh-Git 将 会 在 下 次 打 开 提 示 符 时 被 启 用 总 结 你 已 经 学 会 了 如 何 从 日 常 工 具 中 发 挥 Git 的 强 大 力 量, 以 及 从 自 己 的 程 序 中 访 问 Git 仓 库 的 方 法

Pro Git 中文版

Pro Git 中文版 Pro Git 中 文 版 Sco Chacon * Liam Huang 2014-04-02 * This is the PDF file for the Pro Git book contents. It is licensed under the Creative Commons A ribution-non Commercial-Share Alike 3.0 license. I hope

More information

What is Version Control? What is Git?

What is Version Control? What is Git? Git Littlebtc (Hsiao-Ting Yu) Scott Chacon Pro Git CC-BY-NC-SA-3.0 What is Version Control? What is Git? Local rcs Server Checkout Commit Subversion SVN Server Server git, Mecurial (hg), bazaar (bzr)

More information

像 客 样 使 命令行 徐 东

像 客 样 使 命令行 徐 东 像 客 样 使 命令行 徐 东 1 1.1................................ 1 1.2................................. 3 1.3............................. 4 1.3.1 Linux............................ 5 1.3.2 macos............................

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 Linux 操 作 系 统 基 础 介 绍 课 程 目 标 及 要 求 了 解 Linux 操 作 系 统 的 登 入 方 式 掌 握 常 用 命 令 的 基 本 用 法 能 够 熟 练 在 各 个 目 录 转 换 Outline 1. Linux 操 作 系 统 简 介 2. Linux 操 作 系 统 的 登 录 3. Linux 操 作 系 统 的 目 录 结 构 4. 常 用 命 令 5.

More information

一 Grass 是 什 么 1 简 介 GRASS (Geographic Resources Analysis Support System, 地 理 资 源 分 析 支 持 系 统 ) 是 最 负 盛 名 的 开 源 地 理 信 息 系 统 (GIS) 以 下 是 它 的 一 些 特 点 : 1

一 Grass 是 什 么 1 简 介 GRASS (Geographic Resources Analysis Support System, 地 理 资 源 分 析 支 持 系 统 ) 是 最 负 盛 名 的 开 源 地 理 信 息 系 统 (GIS) 以 下 是 它 的 一 些 特 点 : 1 GRASS 中 文 教 程 作 者 : 广 东 省 东 莞 市 长 安 中 学 文 合 平 E_mail: wenheping@gmail.com 2007 年 9 月 1 一 Grass 是 什 么 1 简 介 GRASS (Geographic Resources Analysis Support System, 地 理 资 源 分 析 支 持 系 统 ) 是 最 负 盛 名 的 开 源 地 理

More information

Microsoft Word - template.doc

Microsoft Word - template.doc HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started

More information

Pro Git

Pro Git Pro Git Scott Chacon, Ben Straub Version 2.1.14, 2018-05-19 目录 许可证.......................................................................................... 1 Scott Chacon 序.................................................................................

More information

Pro Git

Pro Git Pro Git Scott Chacon, Ben Straub Version 2.1.15, 2018-07-27 目录 许可证.......................................................................................... 1 Scott Chacon 序.................................................................................

More information

Pro Git

Pro Git Pro Git Scott Chacon, Ben Straub Version 2.1.9, 2018-04-15 目录 许可证.......................................................................................... 1 Scott Chacon 序.................................................................................

More information

epub 63-3

epub 63-3 3 Solaris S o l a r i s S o l a r i s 2 S o l a r i s s h e l l p a s s w d v i l s c a t p g m o r e r m 3.1 3.1.1 c p c p c o p y c p c p cp source-file destination-file s o u r c e - f i l e c p d e

More information

http://tinyurl.com/amazonprogit color.* p4 edit file 24b9da6552252987aa493b52f8696cd6d3b00373 git clone.git git clone --bare $ yum install curl-devel expat-devel gettext-devel \ openssl-devel

More information

Guide to Install SATA Hard Disks

Guide to Install SATA Hard Disks SATA RAID 1. SATA. 2 1.1 SATA. 2 1.2 SATA 2 2. RAID (RAID 0 / RAID 1 / JBOD).. 4 2.1 RAID. 4 2.2 RAID 5 2.3 RAID 0 6 2.4 RAID 1.. 10 2.5 JBOD.. 16 3. Windows 2000 / Windows XP 20 1. SATA 1.1 SATA Serial

More information

Pro Git

Pro Git Pro Git Table of Contents Pro Git.......................................................................................... 1 Scott Chacon 序..............................................................................

More information

IP505SM_manual_cn.doc

IP505SM_manual_cn.doc IP505SM 1 Introduction 1...4...4...4...5 LAN...5...5...6...6...7 LED...7...7 2...9...9...9 3...11...11...12...12...12...14...18 LAN...19 DHCP...20...21 4 PC...22...22 Windows...22 TCP/IP -...22 TCP/IP

More information

ebook140-8

ebook140-8 8 Microsoft VPN Windows NT 4 V P N Windows 98 Client 7 Vintage Air V P N 7 Wi n d o w s NT V P N 7 VPN ( ) 7 Novell NetWare VPN 8.1 PPTP NT4 VPN Q 154091 M i c r o s o f t Windows NT RAS [ ] Windows NT4

More information

ebook62-1

ebook62-1 1 Red Hat Linux R e d Hat Linux L i n u x X Wi n d o w Red Hat L i n u x 1.1 Red Hat Linux Red Hat 16 M 120 M 3. 5 Intel 386 C D - R O M C D - R O M We b / 1.1.1 L i n u x L i n u 4 Primary Partition Extended

More information

K7VT2_QIG_v3

K7VT2_QIG_v3 ............ 1 2 3 4 5 [R] : Enter Raid setup utility 6 Press[A]keytocreateRAID RAID Type: JBOD RAID 0 RAID 1: 2 7 RAID 0 Auto Create Manual Create: 2 RAID 0 Block Size: 16K 32K

More information

AL-M200 Series

AL-M200 Series NPD4754-00 TC ( ) Windows 7 1. [Start ( )] [Control Panel ()] [Network and Internet ( )] 2. [Network and Sharing Center ( )] 3. [Change adapter settings ( )] 4. 3 Windows XP 1. [Start ( )] [Control Panel

More information

ebook70-5

ebook70-5 5 / 5.1 L i n u x L i n u x X L i n u x 5.1.1 touch t o u c h t o u c h G N U t o u c h # touch newfile # ls -l newfile - r w - r - - r - - 1 bball users 0 Jan 5 12 : 40 n e w f i l e t o u c h 0 # > newfile2

More information

软件测试(TA07)第一学期考试

软件测试(TA07)第一学期考试 一 判 断 题 ( 每 题 1 分, 正 确 的, 错 误 的,20 道 ) 1. 软 件 测 试 按 照 测 试 过 程 分 类 为 黑 盒 白 盒 测 试 ( ) 2. 在 设 计 测 试 用 例 时, 应 包 括 合 理 的 输 入 条 件 和 不 合 理 的 输 入 条 件 ( ) 3. 集 成 测 试 计 划 在 需 求 分 析 阶 段 末 提 交 ( ) 4. 单 元 测 试 属 于 动

More information

A9RF716.tmp

A9RF716.tmp 1 PART I 1 2 3 4 5 6 7 8 Docker Docker Image Container Repository Docker le Docker Docker 8 1 Docker Linux 2 Docker Docker 3 5 Docker 6 Docker volume 7 8 Docker le Docker le 1 C H A P T E R 1 CPU Data

More information

Microsoft Word - PS2_linux_guide_cn.doc

Microsoft Word - PS2_linux_guide_cn.doc Linux For $ONY PlayStatioin2 Unofficall General Guide Language: Simplified Chinese First Write By Beter Hans v0.1 Mail: hansb@citiz.net Version: 0.1 本 人 是 菜 鸟 + 小 白 欢 迎 指 正 错 误 之 处, 如 果 您 有 其 他 使 用 心 得

More information

lect03.ppt

lect03.ppt Linux 操 作 系 统 Linux 基 础 主 要 内 容 q 使 用 Linux q Linux 的 两 种 登 录 方 式 q 字 符 操 作 环 境 和 X Windows 系 统 q Linux 图 形 界 面 基 本 操 作 q Linux 命 令 的 使 用 方 式 q Linux 一 些 常 用 命 令 1 2 一 些 基 本 术 语 u 命 令 (Command) 给 计 算 机

More information

AXIS P7224 Video Encoder Blade – Installation Guide

AXIS P7224 Video Encoder Blade – Installation Guide 安 装 指 南 AXIS P7224 刀 片 视 频 编 码 器 中 文 法 律 考 虑 事 项 视 频 和 音 频 监 视 可 能 会 受 法 律 限 制, 各 个 国 家 / 地 区 的 法 律 会 有 所 不 同 如 将 本 产 品 用 于 监 控 目 的, 需 要 先 检 查 是 否 符 合 你 所 在 区 域 内 的 法 律 规 定 本 产 品 包 括 四 个 (4) H.264 解 码

More information

Windows XP

Windows XP Windows XP What is Windows XP Windows is an Operating System An Operating System is the program that controls the hardware of your computer, and gives you an interface that allows you and other programs

More information

Sophos Central 快速安裝手冊

Sophos Central 快速安裝手冊 Sophos Central 快速安裝手冊 1 1. Sophos Central...5 2....9 3....13 3.1. Enduser Protection...13 3.2. Intercept X...21 3.3....28 3.4....36 3.5....45 3.5.1...45 3.5.2...50 3.5.3...54 3.5.4...57 3.5.5...60 3.6...63

More information

Linux 操作系统课程社区创作

Linux 操作系统课程社区创作 学 号 14284060xx 等 第 苏 州 大 学 实 验 报 告 Linux 操 作 系 统 课 程 社 区 创 作 院 ( 系 ) 名 称 : 电 子 信 息 学 院 专 业 名 称 : 14 通 信 工 程 ( 嵌 入 式 培 养 ) 学 生 姓 名 : 某 某 某 课 程 名 称 : Linux 操 作 系 统 2015-2016 学 年 第 一 学 期 1 摘 要 这 是 摘 要 主 要

More information

AL-MX200 Series

AL-MX200 Series PostScript Level3 Compatible NPD4760-00 TC Seiko Epson Corporation Seiko Epson Corporation ( ) Seiko Epson Corporation Seiko Epson Corporation Epson Seiko Epson Corporation Apple Bonjour ColorSync Macintosh

More information

epub83-1

epub83-1 C++Builder 1 C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r 1.1 1.1.1 1-1 1. 1-1 1 2. 1-1 2 A c c e s s P a r a d o x Visual FoxPro 3. / C / S 2 C + + B u i l d e r / C

More information

(Microsoft Word - 1-\302\262\244\266.doc)

(Microsoft Word - 1-\302\262\244\266.doc) 勞 工 事 務 局 目 錄 ( 一 ) 勞 工 事 務 局 工 作 範 疇... P.2 ( 二 ) 工 作 亮 點... P.3-6 ( 三 ) 工 作 概 況... P.7-18 ( 四 ) 交 流 活 動 剪 影... P.19-20 ( 五 ) 2013 年 統 計 數 據... P.21-32 第 1 頁 勞 工 事 務 局 ( 一 ) 勞 工 事 務 局 勞 工 事 務 局 是 負 責

More information

徐汇教育214/3月刊 重 点 关 注 高中生异性交往的小团体辅导 及效果研究 颜静红 摘 要 采用人际关系综合诊断量表 郑日昌编制并 与同性交往所不能带来的好处 带来稳定感和安全感 能 修订 对我校高一学生进行问卷测量 实验组前后测 在 够度过更快乐的时光 获得与别人友好相处的经验 宽容 量表总分和第 4 项因子分 异性交往困扰 上均有显著差 大度和理解力得到发展 得到掌握社会技术的机会 得到 异

More information

Microsoft Word - ChineseSATII .doc

Microsoft Word - ChineseSATII .doc 中 文 SAT II 冯 瑶 一 什 么 是 SAT II 中 文 (SAT Subject Test in Chinese with Listening)? SAT Subject Test 是 美 国 大 学 理 事 会 (College Board) 为 美 国 高 中 生 举 办 的 全 国 性 专 科 标 准 测 试 考 生 的 成 绩 是 美 国 大 学 录 取 新 生 的 重 要 依

More information

PowerPoint Presentation

PowerPoint Presentation TOEFL Practice Online User Guide Revised September 2009 In This Guide General Tips for Using TOEFL Practice Online Directions for New Users Directions for Returning Users 2 General Tips To use TOEFL Practice

More information

Measurement Studio Expands Your Test and Measurement Programming Power

Measurement Studio Expands Your Test and Measurement Programming Power NI-DAQmx NI-DAQ NI-DAQmx NI-DAQ NI-DAQmx NI-DAQmx NI-DAQ NI-DAQmx NI-DAQmx LabVIEW LabWindows/CVI ANSI C Measurement Studio Visual Studio I/O 1. I/O API I/O NI NI NI NI ADE 1.NI-DAQmx NI & MAX DAQ Assistant

More information

ebook140-9

ebook140-9 9 VPN VPN Novell BorderManager Windows NT PPTP V P N L A V P N V N P I n t e r n e t V P N 9.1 V P N Windows 98 Windows PPTP VPN Novell BorderManager T M I P s e c Wi n d o w s I n t e r n e t I S P I

More information

ch08.PDF

ch08.PDF 8-1 CCNA 8.1 CLI 8.1.1 8-2 8-3 8.1.21600 2500 1600 2500 / IOS 8-4 8.2 8.2.1 A 5 IP CLI 1600 2500 8-5 8.1.2-15 Windows 9598NT 2000 HyperTerminal Hilgraeve Microsoft Cisco HyperTerminal Private Edition (PE)

More information

ebook35-2

ebook35-2 2 2.1 Linux login Login: < > Password: < > Linux r o o t l o g o u t 2.2 Linux X Window Linux Linux Bourne ( b s h ) C ( c s h ) Korn ( k s h ) Bourne Steven Bourne UNIX Bourne bash Bourne C Bill Joy Bourne

More information

國立桃園高中96學年度新生始業輔導新生手冊目錄

國立桃園高中96學年度新生始業輔導新生手冊目錄 彰 化 考 區 104 年 國 中 教 育 會 考 簡 章 簡 章 核 定 文 號 : 彰 化 縣 政 府 104 年 01 月 27 日 府 教 學 字 第 1040027611 號 函 中 華 民 國 104 年 2 月 9 日 彰 化 考 區 104 年 國 中 教 育 會 考 試 務 會 編 印 主 辦 學 校 : 國 立 鹿 港 高 級 中 學 地 址 :50546 彰 化 縣 鹿 港 鎮

More information

Ø Ø Microsoft Project Ø Zou Zhige VLSI 2

Ø Ø Microsoft Project Ø Zou Zhige VLSI 2 Ø Ø Microsoft Project Ø Zou Zhige VLSI 2 Ø Ø Ø Zou Zhige VLSI 3 Ø Ø Zou Zhige VLSI 4 Ø CVS remote access edit flag Ø CVS, Zou Zhige VLSI 5 Ø Zou Zhige VLSI 6 l l l Zou Zhige VLSI 7 Ø ( ) CVS : ( ) ( start)

More information

ebook70-11

ebook70-11 11 L i n u x p i n e M e s s e n g e r P P P I S 11.1 s e n d m a i l U N I X O p e n L i n u x U N I X O p e n L i n u x O p e n L i n u x s e n d m a i l O p e n L i n u x ( 11-1 ) 11-1 O p e n L i n

More information

本文由筱驀釹贡献

本文由筱驀釹贡献 本 文 由 筱 驀 釹 贡 献 ppt 文 档 可 能 在 WAP 端 浏 览 体 验 不 佳 建 议 您 优 先 选 择 TXT, 或 下 载 源 文 件 到 本 机 查 看 Linux 操 作 系 统 Linux 操 作 系 统 第 一 部 分 介 绍 与 安 装 Linux 的 由 来 : Linux 的 由 来 : 的 由 来 Linus Torvalds 1.Linux 的 版 本 1.Linux

More information

UDC Empirical Researches on Pricing of Corporate Bonds with Macro Factors 厦门大学博硕士论文摘要库

UDC Empirical Researches on Pricing of Corporate Bonds with Macro Factors 厦门大学博硕士论文摘要库 10384 15620071151397 UDC Empirical Researches on Pricing of Corporate Bonds with Macro Factors 2010 4 Duffee 1999 AAA Vasicek RMSE RMSE Abstract In order to investigate whether adding macro factors

More information

Pro Git 中文版 Sco Chacon * Liam Huang 2014-04-01 * This is the PDF file for the Pro Git book contents. It is licensed under the Creative Commons A ribution-non Commercial-Share Alike 3.0 license. I hope

More information

EK-STM32F

EK-STM32F STMEVKIT-STM32F10xx8 软 件 开 发 入 门 指 南 目 录 1 EWARM 安 装... 1 1.1 第 一 步 : 在 线 注 册... 1 1.2 第 二 步 : 下 载 软 件... 2 1.3 第 三 步 : 安 装 EWARM... 3 2 基 于 STMEVKIT-STM32F10xx8 的 示 例 代 码 运 行... 6 2.1 GPIO Demo... 6 2.2

More information

ebook8-30

ebook8-30 3 0 C C C C C C++ C + + C++ GNU C/C++ GNU egcs UNIX shell s h e l l g a w k P e r l U N I X I / O UNIX shell awk P e r l U N I X C C C C C C U N I X 30.1 C C U N I X 70 C C U N I X U N I X U N I X C Dennis

More information

Microsoft Word - linux命令及建议.doc

Microsoft Word - linux命令及建议.doc Linux 操 作 系 统 命 令 集 1 基 本 命 令 查 看 系 统 信 息 : uname -a 修 改 密 码 : passwd 退 出 : logout(exit) 获 取 帮 助 : man commands 2 文 件 和 目 录 命 令 显 示 当 前 工 作 目 录 : pwd 改 变 所 在 目 录 : cd cd - 切 换 到 上 一 次 使 用 的 目 录 cd 切 换

More information

麼? What can one do to build a close relationship with a quiet man? Why? 兒 童 組 建 議 : 幫 助 兒 童 了 解 男 人 的 親 密 週 期 好 像 橡 根 箍 一 樣 問 : 為 什 麼 你 的 父 親 兄 弟 男 同

麼? What can one do to build a close relationship with a quiet man? Why? 兒 童 組 建 議 : 幫 助 兒 童 了 解 男 人 的 親 密 週 期 好 像 橡 根 箍 一 樣 問 : 為 什 麼 你 的 父 親 兄 弟 男 同 Series on Heart Winning Communication (6) < 贏 取 人 心 的 溝 通 > 系 列 (6) 建 立 親 密 度 1. 請 看 羅 馬 書 12:10 你 覺 得 你 和 男 性 建 立 親 密 的 關 係 容 易 嗎? 請 解 釋?Do you feel it is easy for you to establish a close relationship

More information

ebook70-14

ebook70-14 Linux 1 4 1 5 1 6 1 7 1 8 1 9 S t a r O ff i c e 2 0 L i n u x 1 4 O p e n L i n u x O p e n L i n u x C D - R O M O p e n L i n u x C o r e l WordPerfect 8 for Linux S t a r D i v i s i o n S t a r O

More information

Eclipse C C++, or

Eclipse C C++,  or Eclipse C C++, Emailctchen@pl.csie.ntut.edu.tw or s1669021@ntut.edu.tw, s2598003@ntut.edu.tw http://pl.csie.ntut.edu.tw/~ctchen, http://www.ntut.edu.tw/~s2598003/ 2004/9/10 (0.02 ) Eclipse http://www.eclipse.org

More information

網路安全:理論與實務 第二版

網路安全:理論與實務 第二版 第 10 章 :Wireshark 封 包 分 析 軟 體 10-1 Wireshark 簡 介 10-2 Wireshark 的 安 裝 方 法 10-3 Wireshark 的 使 用 Wireshark 簡 介 - 發 展 歷 史 Wireshark (http://www.wireshark.org/) 是 一 個 開 放 原 始 碼 (open source software) 軟 體,

More information

摘 要 1. GSLB: 全 局 负 载 均 衡 2. SLB: 服 务 器 负 载 均 衡 四 层 交 换 LVS 七 层 交 换 Nginx 3. Heartbeat 实 现 HA 4. MySQL 数 据 库 集 群 5. 集 群 环 境 下 的 存 储 备 份 6. 集 群 的 监 控 及

摘 要 1. GSLB: 全 局 负 载 均 衡 2. SLB: 服 务 器 负 载 均 衡 四 层 交 换 LVS 七 层 交 换 Nginx 3. Heartbeat 实 现 HA 4. MySQL 数 据 库 集 群 5. 集 群 环 境 下 的 存 储 备 份 6. 集 群 的 监 控 及 网 站 集 群 架 构 利 用 开 源 软 件 构 建 高 可 用 高 性 能 可 扩 展 的 集 群 系 统 兰 锋 bluedata@gmail.com 摘 要 1. GSLB: 全 局 负 载 均 衡 2. SLB: 服 务 器 负 载 均 衡 四 层 交 换 LVS 七 层 交 换 Nginx 3. Heartbeat 实 现 HA 4. MySQL 数 据 库 集 群 5. 集 群 环 境

More information

ebook71-8

ebook71-8 8 8. 2. 1 8. 2. 2 l i n u x c o n f 8. 2. 3 8. 2. 4 8. 2. 5 8. 2. 6 8. 2. 7 l i n u x c o n f 8. 2. 8 s h a d o w 8. 2. 9 s h a d o w 8. 2. 10 s h a d o w 8. 2. 11 8. 2. 1 2 8. 2. 1 3 8. 2. 1 4 l i n u

More information

2-7.FIT)

2-7.FIT) 文 化 园 地 8 2009 年 8 月 18 日 星 期 二 E-mail:liuliyuan@qunlitimes.com 群 立 文 化 感 受 今 天 你 开 心 了 吗? 周 传 喜 群 雄 争 立 竞 争 意 识 ; 傲 立 群 雄 奋 斗 目 标, 这 几 句 话 一 直 是 群 立 的 文 化 和 方 针, 也 同 样 是 我 很 喜 欢 的 座 右 铭 我 想 这 几 句 话 生

More information

前 言 香 港 中 文 大 學 優 質 學 校 改 進 計 劃 ( 下 稱 計 劃 ) 團 隊 自 1998 年 起 積 極 於 本 地 推 動 理 論 及 實 踐 並 重 的 學 校 改 進 工 作, 並 逐 步 發 展 成 為 本 地 最 具 規 模 的 校 本 支 援 服 務 品 牌, 曾 支

前 言 香 港 中 文 大 學 優 質 學 校 改 進 計 劃 ( 下 稱 計 劃 ) 團 隊 自 1998 年 起 積 極 於 本 地 推 動 理 論 及 實 踐 並 重 的 學 校 改 進 工 作, 並 逐 步 發 展 成 為 本 地 最 具 規 模 的 校 本 支 援 服 務 品 牌, 曾 支 香 港 中 文 大 學 香 港 教 育 研 究 所 優 質 學 校 改 進 計 劃 : 學 習 差 異 支 援 及 學 校 起 動 計 劃 聯 合 主 辦 中 學 聯 校 教 師 專 業 發 展 日 主 題 : 照 顧 學 習 差 異 日 期 :2011 年 12 月 9 日 ( 星 期 五 ) 時 間 : 上 午 9 時 正 至 下 午 4 時 15 分 地 點 : 樂 善 堂 余 近 卿 中 學

More information

1.ai

1.ai HDMI camera ARTRAY CO,. LTD Introduction Thank you for purchasing the ARTCAM HDMI camera series. This manual shows the direction how to use the viewer software. Please refer other instructions or contact

More information

Microsoft Word zw

Microsoft Word zw 第 1 章 Android 概述 学习目标 : Android Android Android Studio Android Android APK 1.1 1. 智能手机的定义 Smartphone 2. 智能手机的发展 1973 4 3 PC IBM 1994 IBM Simon PDA PDA Zaurus OS 1996 Nokia 9000 Communicator Nokia 9000

More information

4. 每 组 学 生 将 写 有 习 语 和 含 义 的 两 组 卡 片 分 别 洗 牌, 将 顺 序 打 乱, 然 后 将 两 组 卡 片 反 面 朝 上 置 于 课 桌 上 5. 学 生 依 次 从 两 组 卡 片 中 各 抽 取 一 张, 展 示 给 小 组 成 员, 并 大 声 朗 读 卡

4. 每 组 学 生 将 写 有 习 语 和 含 义 的 两 组 卡 片 分 别 洗 牌, 将 顺 序 打 乱, 然 后 将 两 组 卡 片 反 面 朝 上 置 于 课 桌 上 5. 学 生 依 次 从 两 组 卡 片 中 各 抽 取 一 张, 展 示 给 小 组 成 员, 并 大 声 朗 读 卡 Tips of the Week 课 堂 上 的 英 语 习 语 教 学 ( 二 ) 2015-04-19 吴 倩 MarriottCHEI 大 家 好! 欢 迎 来 到 Tips of the Week! 这 周 我 想 和 老 师 们 分 享 另 外 两 个 课 堂 上 可 以 开 展 的 英 语 习 语 教 学 活 动 其 中 一 个 活 动 是 一 个 充 满 趣 味 的 游 戏, 另 外

More information

软件概述

软件概述 Cobra DocGuard BEIJING E-SAFENET SCIENCE & TECHNOLOGY CO.,LTD. 2003 3 20 35 1002 010-82332490 http://www.esafenet.com Cobra DocGuard White Book 1 1....4 1.1...4 1.2 CDG...4 1.3 CDG...4 1.4 CDG...5 1.5

More information

Serial ATA ( Silicon Image SiI3114)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 5 (4) S A T A... 8 (5) S A T A... 10

Serial ATA ( Silicon Image SiI3114)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 5 (4) S A T A... 8 (5) S A T A... 10 Serial ATA ( Silicon Image SiI3114)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 5 (4) S A T A... 8 (5) S A T A... 10 Ác Åé å Serial ATA ( Silicon Image SiI3114) S A T A (1) SATA (2)

More information

Cygwin & vim

Cygwin & vim Cygwin & vim Yu Hsiang Zheng (Slighten) Outline Shell Cygwin vim 1/21 What is a computer 2/21 What is a computer 拿 地 球 來 做 比 喻 的 話 kernel: 地 心 shell: 地 殼 application: 房 子 各 種 建 築 物 shell = command interpreter

More information

Junos Pulse Mobile Security R1 2012, Juniper Networks, Inc.

Junos Pulse Mobile Security R1 2012, Juniper Networks, Inc. Junos Pulse Mobile Security 4.0 2012 6 R1 2012, Juniper Networks, Inc. Junos Pulse Mobile Security Juniper Networks, Inc. 1194 North Mathilda Avenue Sunnyvale, California 94089 408-745-2000 www.juniper.net

More information

蔡 氏 族 譜 序 2

蔡 氏 族 譜 序 2 1 蔡 氏 族 譜 Highlights with characters are Uncle Mike s corrections. Missing or corrected characters are found on pages 9, 19, 28, 34, 44. 蔡 氏 族 譜 序 2 3 福 建 仙 遊 赤 湖 蔡 氏 宗 譜 序 蔡 氏 之 先 出 自 姬 姓 周 文 王 第 五 子

More information

宁夏专业技术人员服务平台

宁夏专业技术人员服务平台 宁 夏 专 业 技 术 人 员 服 务 平 台 职 称 申 报 系 统 版 本 号 :1.0 ( 版 本 处 于 变 动 中, 请 您 随 时 下 载 新 版 ) 使 用 说 明 书 2014 年 3 月 4 日 目 录 一 使 用 要 求... 1 二 进 入 系 统... 2 三 用 户 注 册... 4 四 完 善 个 人 基 本 信 息... 6 五 职 称 申 报... 8 六 打 印 确

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 9 [P.11] : Dev C++ [P.12] : http://c.feis.tw [P.13] [P.14] [P.15] [P.17] [P.23] Dev C++ [P.24] [P.27] [P.34] C / C++ [P.35] 10 C / C++ C C++ C C++ C++ C ( ) C++

More information

國立竹北高級中學參加101-2學年度

國立竹北高級中學參加101-2學年度 個 人 申 請 入 學 第 二 階 段 調 查 表 甄 大 學 校 系 台 中 教 育 大 學 資 訊 工 程 系 班 級 301 座 號 36 性 別 女 自 傳 ( 字 數 : ) ( 共 需 : 5 鐘 x2 即 時 演 講 ( 為 ) 鐘 ) 文 ( 字 數 : ) ( ) ˇ 考 前 繳 交 當 天 繳 交 4 位 教 授 對 4 個 學 生 共 成 2 關 文 1. 進 去 第 一 間

More information

可 愛 的 動 物 小 五 雷 雅 理 第 一 次 小 六 甲 黃 駿 朗 今 年 暑 假 發 生 了 一 件 令 人 非 常 難 忘 的 事 情, 我 第 一 次 參 加 宿 營, 離 開 父 母, 自 己 照 顧 自 己, 出 發 前, 我 的 心 情 十 分 緊 張 當 到 達 目 的 地 後

可 愛 的 動 物 小 五 雷 雅 理 第 一 次 小 六 甲 黃 駿 朗 今 年 暑 假 發 生 了 一 件 令 人 非 常 難 忘 的 事 情, 我 第 一 次 參 加 宿 營, 離 開 父 母, 自 己 照 顧 自 己, 出 發 前, 我 的 心 情 十 分 緊 張 當 到 達 目 的 地 後 郭家朗 許鈞嵐 劉振迪 樊偉賢 林洛鋒 第 36 期 出版日期 28-3-2014 出版日期 28-3-2014 可 愛 的 動 物 小 五 雷 雅 理 第 一 次 小 六 甲 黃 駿 朗 今 年 暑 假 發 生 了 一 件 令 人 非 常 難 忘 的 事 情, 我 第 一 次 參 加 宿 營, 離 開 父 母, 自 己 照 顧 自 己, 出 發 前, 我 的 心 情 十 分 緊 張 當 到 達 目

More information

AXIS M7014/M7010

AXIS M7014/M7010 安 装 指 南 AXIS M7014 视 频 编 码 器 中 文 AXIS M7010 视 频 编 码 器 关 于 本 文 档 本 文 档 包 含 网 络 安 装 AXIS M7014/M7010 说 明 安 装 该 产 品 时, 拥 有 网 络 经 验 将 会 有 一 定 的 帮 助 法 律 考 虑 事 项 视 频 和 音 频 监 视 可 能 会 受 法 律 限 制, 各 个 国 家 / 地 区

More information

1 SQL Server 2005 SQL Server Microsoft Windows Server 2003NTFS NTFS SQL Server 2000 Randy Dyess DBA SQL Server SQL Server DBA SQL Server SQL Se

1 SQL Server 2005 SQL Server Microsoft Windows Server 2003NTFS NTFS SQL Server 2000 Randy Dyess DBA SQL Server SQL Server DBA SQL Server SQL Se 1 SQL Server 2005 DBA Microsoft SQL Server SQL ServerSQL Server SQL Server SQL Server SQL Server SQL Server 2005 SQL Server 2005 SQL Server 2005 o o o SQL Server 2005 1 SQL Server 2005... 3 2 SQL Server

More information

ebook 185-6

ebook 185-6 6 Red Hat Linux DB2 Universal Database 6.1 D B 2 Red Hat D B 2 Control Center D B 2 D B 2 D B 2 6.1 DB2 Universal Database [DB2]6.1 D B 2 O LT P O L A P D B 2 I B M P C We e k D B 2 D B 2 L i n u x Windows

More information

93年各縣國中教師甄試最新考情.doc

93年各縣國中教師甄試最新考情.doc 93 7/8()~7/13() 7/11()~7/13() 7/17() 7/18() 7/18() 7/19() 7/21() 40% 20%( ( )) 20%( ) 1 35% 25% ( ) 70% 10%( ) 60% 1 20% 10% ( ) 6/1()~6/11() 6/12()~6/14() 6/19() 6/21() 6/26() 6/26()22:00 7/3() 40%( )

More information

1 IT IT IT IT Virtual Machine, VM VM VM VM Operating Systems, OS IT

1 IT IT IT IT Virtual Machine, VM VM VM VM Operating Systems, OS IT 1 IT IT IT IT Virtual Machine, VM VM VM VM Operating Systems, OS IT Chapter 1 了解虛擬化技術種類 硬體 / 平台 / 伺服器虛擬化 VM VM VM CPU Hypervisor VMM Virtual Machine Manager VM Host OS VM VM Guest OS Host OS CPU VM Hyper-V

More information

Microsoft Word - CX VMCO 3 easy step v1.doc

Microsoft Word - CX VMCO 3 easy step v1.doc Abacus Fully Automated Process of VMCO on CX, KA, CPH & KAH 16 Nov 2009 To streamline the VMCO handling on CX, KA, CPH & KAH, Abacus is pleased to inform you that manual submission of VMCO to CX/KA/CPH/KAH

More information

Olav Lundström MicroSCADA Pro Marketing & Sales 2005 ABB - 1-1MRS755673

Olav Lundström MicroSCADA Pro Marketing & Sales 2005 ABB - 1-1MRS755673 Olav Lundström MicroSCADA Pro Marketing & Sales 2005 ABB - 1 - Contents MicroSCADA Pro Portal Marketing and sales Ordering MicroSCADA Pro Partners Club 2005 ABB - 2 - MicroSCADA Pro - Portal Imagine that

More information

Message from the Chief Editor 01 News and features 10 12 Best way to eat 15 Food story 19 Good taste 30 DIY Eating at home

Message from the Chief Editor 01 News and features 10 12 Best way to eat 15 Food story 19 Good taste 30 DIY Eating at home magazine June. 2009 Message from the Chief Editor 01 News and features 10 12 Best way to eat 15 Food story 19 Good taste 30 DIY Eating at home chief editor Liu Ziting pamelalzt@hotmail.com "Cooking and

More information

一 課 後 社 團 名 稱 :B02. 直 排 輪 校 隊 C 班 ( 校 隊 班 ) 二 授 課 教 師 : 劉 輔 人 助 教 : 杜 翊 嘉 2011 2013 世 界 盃 滑 輪 溜 冰 錦 標 賽 世 界 冠 軍 榮 獲 VOUGE 時 尚 雜 誌 專 訪 同 週 一 校 隊 班 介 紹

一 課 後 社 團 名 稱 :B02. 直 排 輪 校 隊 C 班 ( 校 隊 班 ) 二 授 課 教 師 : 劉 輔 人 助 教 : 杜 翊 嘉 2011 2013 世 界 盃 滑 輪 溜 冰 錦 標 賽 世 界 冠 軍 榮 獲 VOUGE 時 尚 雜 誌 專 訪 同 週 一 校 隊 班 介 紹 一 課 後 社 團 名 稱 :B01. 直 排 輪 綜 合 B 班 ( 綜 合 班 ) 二 授 課 教 師 : 郭 佳 佩 助 教 : 同 週 一 校 隊 B 班 介 紹 同 週 一 綜 合 A 班 第 1 週 同 週 一 綜 合 A 班 第 2 週 同 週 一 綜 合 A 班 第 3 週 同 週 一 綜 合 A 班 第 4 週 同 週 一 綜 合 A 班 第 5 週 同 週 一 綜 合 A 班 第

More information

目 录 Linux Mint 简介... 3 Linux Mint 安装... 6 Linux Mint 桌面初识... 18 软件管理...30 小技巧...40 总结...42

目 录 Linux Mint 简介... 3 Linux Mint 安装... 6 Linux Mint 桌面初识... 18 软件管理...30 小技巧...40 总结...42 官方用户手册 Linux Mint 9 Isadora 主版本 翻译 jluliuchao 皮蛋侠 第 1 页/共 42 页 目 录 Linux Mint 简介... 3 Linux Mint 安装... 6 Linux Mint 桌面初识... 18 软件管理...30 小技巧...40 总结...42 Linux Mint 简介 Linux Mint 是一种计算机操作系统 它被设计运行于现今大部分硬件系统

More information

Progress Report of BESIII Slow Control Software Development

Progress Report of BESIII Slow Control Software Development BESIII 慢控制系统高压和 VME 监控 系统的设计和实现 陈锡辉 BESIII 慢控制组 2006-4-27 Outline Design and implementation of HV system Features Implementation Brief introduction to VME system Features Implementation of a demo Tasks

More information

untitled

untitled Unix Ka-Lok Ng () Department of Biological Sciences and Biotechnology() Taichung Healthcare and Management University (O) 04-23323456 x1856 3408 Teach the student how to use Linux system using TEXT mode

More information

Tel: Fax: TTP-344M/246M /

Tel: Fax: TTP-344M/246M / TTP-344M/246M / True Type font David Turner, Robert Wilhelm Werner Lemberg The Free Type Project 235 16 8 2 i- TTP-344M/246M...1 1.1...1 1.2...1 1.2.1...1 1.2.2 /...2 1.2.3...2 1.2.4...2 1.3...3 1.4...3

More information

99 學年度班群總介紹 第 370 期 班群總導 陳怡靜 G45 班群總導 陳怡靜(河馬) A 家 惠如 家浩 T 格 宜蓁 小 霖 怡 家 M 璇 均 蓁 雴 家 數學領域 珈玲 國燈 370-2 英領域 Kent

99 學年度班群總介紹 第 370 期 班群總導 陳怡靜 G45 班群總導 陳怡靜(河馬) A 家 惠如 家浩 T 格 宜蓁 小 霖 怡 家 M 璇 均 蓁 雴 家 數學領域 珈玲 國燈 370-2 英領域 Kent 2010 年 8 月 27 日 出 刊 精 緻 教 育 宜 蘭 縣 公 辦 民 營 人 國 民 中 小 學 財 團 法 人 人 適 性 教 育 基 金 會 承 辦 地 址 : 宜 蘭 縣 26141 頭 城 鎮 雅 路 150 號 (03)977-3396 http://www.jwps.ilc.edu.tw 健 康 VS. 學 習 各 位 合 夥 人 其 實 都 知 道, 我 是 個 胖 子, 而

More information

C/C++ - 字符输入输出和字符确认

C/C++ - 字符输入输出和字符确认 C/C++ Table of contents 1. 2. getchar() putchar() 3. (Buffer) 4. 5. 6. 7. 8. 1 2 3 1 // pseudo code 2 read a character 3 while there is more input 4 increment character count 5 if a line has been read,

More information

Microsoft PowerPoint - lect01.ppt

Microsoft PowerPoint - lect01.ppt Linux 操 作 系 统 潘 建 瑜 华 东 师 范 大 学 数 学 系 jypan@math.ecnu.edu.cn Linux 操 作 系 统 教 材 : 以 课 堂 讲 义 为 主 上 课 时 间 : 周 五 5 6 7 第 三 教 学 楼 231 上 机 时 间 : 周 五 5 6 7 数 学 楼 200B 机 房 ( 暂 定 于 第 3 7 10 13 16 18 周 ) 课 程 主 页

More information

ebook70-13

ebook70-13 1 3 I S P O p e n L i n u x Point to Point Protocol P P P I S P L i n u x 10 L i n u x World Wide We b 13.1 We b f t p ( ) f t p (File Transfer Protocol F T P ) F T P g e t p u t 13. 1. 1 F T P f t p n

More information

Microsoft Word - Final Exam Review Packet.docx

Microsoft Word - Final Exam Review Packet.docx Do you know these words?... 3.1 3.5 Can you do the following?... Ask for and say the date. Use the adverbial of time correctly. Use Use to ask a tag question. Form a yes/no question with the verb / not

More information

Lorem ipsum dolor sit amet, consectetuer adipiscing elit

Lorem ipsum dolor sit amet, consectetuer adipiscing elit English for Study in Australia 留 学 澳 洲 英 语 讲 座 Lesson 3: Make yourself at home 第 三 课 : 宾 至 如 归 L1 Male: 各 位 朋 友 好, 欢 迎 您 收 听 留 学 澳 洲 英 语 讲 座 节 目, 我 是 澳 大 利 亚 澳 洲 广 播 电 台 的 节 目 主 持 人 陈 昊 L1 Female: 各 位

More information

Pro Git Scott Chacon * 2010-07-02 * This is the PDF file for the Pro Git book contents. It is licensed under the Creative Commons Attribution-Non Commercial-Share Alike 3.0 license. I hope you enjoy it,

More information

ebook140-11

ebook140-11 11 VPN Windows NT4 B o r d e r M a n a g e r VPN VPN V P N V P N V P V P N V P N TCP/IP 11.1 V P N V P N / ( ) 11.1.1 11 V P N 285 2 3 1. L A N LAN V P N 10MB 100MB L A N VPN V P N V P N Microsoft PPTP

More information

summerCampBookP1~16.pdf

summerCampBookP1~16.pdf 2011 SUMMER CAMPER RULES 1. No campers are allowed to leave camp after check-in. Campers should act with his/her buddy or team. 2. Please follow camp rules and instructions from your counselor at all time.

More information

epub 61-2

epub 61-2 2 Web Dreamweaver UltraDev Dreamweaver 3 We b We b We Dreamweaver UltraDev We b Dreamweaver UltraDev We b We b 2.1 Web We b We b D r e a m w e a v e r J a v a S c r i p t We b We b 2.1.1 Web We b C C +

More information

1505.indd

1505.indd 上 海 市 孙 中 山 宋 庆 龄 文 物 管 理 委 员 会 上 海 宋 庆 龄 研 究 会 主 办 2015.05 总 第 148 期 图 片 新 闻 2015 年 9 月 22 日, 由 上 海 孙 中 山 故 居 纪 念 馆 台 湾 辅 仁 大 学 和 台 湾 图 书 馆 联 合 举 办 的 世 纪 姻 缘 纪 念 孙 中 山 先 生 逝 世 九 十 周 年 及 其 革 命 历 程 特 展

More information

Untitiled

Untitiled 目 立人1 2011 录 目 录 专家视点 权利与责任 班主任批评权的有效运用 齐学红 3 德育园地 立 沿着鲁迅爷爷的足迹 主题队活动案例 郑海娟 4 播下一颗美丽的种子 沿着鲁迅爷爷的足迹 中队活动反思 郑海娟 5 赠人玫瑰 手有余香 关于培养小学生服务意识的一些尝试和思考 孙 勤 6 人 教海纵横 2011 年第 1 期 总第 9 期 主办单位 绍兴市鲁迅小学教育集团 顾 问 编委会主任 编

More information

Microsoft PowerPoint - AWOL - Acrobat Windows Outlook.ppt [Compatibility Mode]

Microsoft PowerPoint - AWOL - Acrobat Windows Outlook.ppt [Compatibility Mode] AWOL Windows - Tips & Tricks Resolution, color depth & refresh rate Background color Service packs Disk cleanup (cleanmgr) Disk defragmentation AWOL Windows Resolution, Color Depth & Refresh Rate The main

More information

coverage2.ppt

coverage2.ppt Satellite Tool Kit STK/Coverage STK 82 0715 010-68745117 1 Coverage Definition Figure of Merit 2 STK Basic Grid Assets Interval Description 3 Grid Global Latitude Bounds Longitude Lines Custom Regions

More information

<4D6963726F736F667420506F776572506F696E74202D20313032A67EB0EAA4A4B2A6B77EA5CDA668A4B8B669B8F4ABC5BEC9C2B2B3F82DA4A4A473A475B0D32E707074>

<4D6963726F736F667420506F776572506F696E74202D20313032A67EB0EAA4A4B2A6B77EA5CDA668A4B8B669B8F4ABC5BEC9C2B2B3F82DA4A4A473A475B0D32E707074> 102 年 國 中 畢 業 生 多 元 進 路 宣 導 教 育 部 宣 導 專 員 中 山 工 商 江 英 綺 國 中 畢 業 生 多 元 入 學 管 道 樂 學 計 畫 五 專 免 試 入 學 技 優 學 生 甄 審 入 學 基 本 學 力 測 驗 申 請 入 學 甄 選 入 學 申 請 抽 籤 入 學 登 記 分 發 入 學 高 中 高 職 高 中 高 職 五 專 音 樂, 美 術, 舞 蹈,

More information

Oracle Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE "P

Oracle Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE P Oracle Solaris Studio 12.3 IDE 2011 12 E26461-01 2 7 8 9 9 Oracle 10 12 14 21 26 27 29 31 32 33 Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE "Project

More information

Unix®t Œ fi z.PDF

Unix®t Œ fi z.PDF 7 9 8 0 $ man umount newfs $ man -a intro $ man -a chown ORDER=C:ADM:ADMN:ADMP:PADM:F:HW 8 1 # catman % ps aux grep chavez chavez 8684 89.5 9.627680 5280? R N 85:26 /home/j90/l988 root 10008 10.0 0.8 1408

More information

<4D6963726F736F667420576F7264202D205F4230365FB942A5CEA668B443C5E9BB73A740B5D8A4E5B8C9A552B1D0A7F75FA6BFB1A4ACFC2E646F63>

<4D6963726F736F667420576F7264202D205F4230365FB942A5CEA668B443C5E9BB73A740B5D8A4E5B8C9A552B1D0A7F75FA6BFB1A4ACFC2E646F63> 運 用 多 媒 體 製 作 華 文 補 充 教 材 江 惜 美 銘 傳 大 學 應 用 中 文 系 chm248@gmail.com 摘 要 : 本 文 旨 在 探 究 如 何 運 用 多 媒 體, 結 合 文 字 聲 音 圖 畫, 製 作 華 文 補 充 教 材 當 我 們 在 進 行 華 文 教 學 時, 往 往 必 須 透 過 教 案 設 計, 並 製 作 補 充 教 材, 方 能 使 教 學

More information

Linux Ubuntu Part Linux Ubuntu Linux UNIX...19 Linux...19 Linux Linux...21 Linux GNU FSF Open So urce.

Linux Ubuntu Part Linux Ubuntu Linux UNIX...19 Linux...19 Linux Linux...21 Linux GNU FSF Open So urce. Linux Ubuntu 10.04 Part 1 17 1 Linux Ubuntu... 18 1-1 Linux... 19 UNIX...19 Linux...19 Linux...20...20 Linux...21 Linux...21 1-2 GNU FSF Open So urce...22 GNU...22 GPL...23...24 1-3 GNU/Linux V.S. Linux...25

More information

WinMDI 28

WinMDI 28 WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS

More information