Plastic SCM - GitSync 指南


简介

您现在可能已经知道,Plastic SCM 是一个功能齐全的 DVCS(分布式版本控制软件)。而且,Plastic SCM 也使用 Git 网络协议。

Plastic SCM 能够将更改直接推送和拉取到任何远程 Git 服务器。这是因为 Plastic 支持通过 https:// 和 git:// 协议来推送和拉取变更集。

此功能立即将 Plastic SCM 转变为与 Git 完全兼容的 DVCS。这一优势意味着您可以在工作站上使用 Plastic 或 Git,并且仍然可以参与 Git 项目(GitHub、CodePlex 等等)。

Plastic - 推送/拉取 - Git

GitSync 是什么?

这可以是第一种方法:GitSync 是连接到 GitHub 的本机 Windows DVCS。因此,它实际上会将 Plastic SCM 变成成熟的 Windows Git 客户端。

注意:从技术上讲,GitSync 并不是新的 Git 客户端:您将在客户端使用 Plastic SCM,但能够推送/拉取到 Git 服务器(使用 https 或 Git 协议)。

假设您是一名使用 GitHub(或 Bitbucket,或 CodePlex)的开发者。无论如何,总有一些方面是您喜欢的:使用基于 Cloud 的存储库存储您的代码,使用 Windows 进行开发。也有您不喜欢的一些方面:被迫使用 CLI,缺乏非常棒的 GUI 工具。

因此,您希望拥有一切:Cloud 存储库、DVCS 功能和适用于 Windows 的出色工具。

使用 GitSync,您就能获得这些。


功能列表

GitSync 的功能如下:

  • 直接推送/拉取 - 包括所有提交、注释、分支、合并跟踪和标记。
  • 添加/删除或移动文件 - 两端没有任何限制。
  • 完全合并跟踪 - 您可以在 Git 端合并,Plastic 将识别合并跟踪。并且,在 Plastic 到 Git 方向也是如此。这就是 Plastic SCM 和 Git 作为完整 DVCS 的优势!
  • 冲突管理 - 可以同时在 Git 和 Plastic 端的同一分支上进行更改,Plastic 将处理数据交换、拉取更改、请求用户运行合并,然后推送已解决的冲突(就像使用完整 Git 设置时所做的那样)。

工作原理

由于一张图胜过千言万语,那就让我们一步一步,完成整个的推送和拉取过程。


初始场景

您有一个 Git 仓库,然后您将其拉到 Plastic 上。结果,您将获得一个精确的克隆,其中位于 Git 中的分支和提交现在已转换到 Plastic,而这个过程中最出色的地方在于能够在分支资源管理器中进行呈现:

GitSync 的工作原理 - 初始场景

在 Git 端创建新的提交

下一张图显示了在 Git 端创建新提交时会发生什么,以及从 Plastic SCM 进行的拉取如何检索该提交。

该图不只是执行简单的更改,而且显示了从 big_feature 分支到 master 的合并。Plastic SCM 中的结果模拟了 Git 端发生的情况,并在 Plastic 分支资源管理器上添加了合并链接(呈现为绿线):

GitSync 的工作原理 - 在 Git 端创建新的提交

在 Plastic 端创建新的变更集

下一步是在 Plastic SCM 中执行更改并将更改推送到 Git。为了创建更完整的示例,我们不仅会创建新的变更集,还会执行合并。

变更集 67 在 Plastic 端创建,然后推送到 Git。如下图所示,合并信息(Git 存储库上的多个父级)也会从 Plastic 发送到 Git。

GitSync 的工作原理 - 在 Plastic 端创建新的提交

到目前为止,更改是在一端或另一端进行的,但不是同时在两端进行的。


同时执行更改:冲突

下图显示了当开发者同时在同一个分支上工作时会发生什么情况。在 Git(绿色)中创建了一个新提交,在 Plastic(橙色)中创建了另一个新提交:

GitSync 的工作原理 - 同时执行更改:冲突

如果 Plastic 开发者尝试推送到 Git,则会出现错误,因为存在冲突性更改(在纯 Plastic 或纯 Git 环境的类似场景中会发生相同的情况)。需要遵循的步骤如下:

  1. 首先从 Git 拉取更改
  2. 此时将创建一个新的“子分支”,并会正确放置 88ffa 变更集。
    GitSync 的工作原理 - 同时执行更改:冲突
  3. 接下来的步骤是解决 Plastic SCM 端的合并冲突,然后完成推送:
    GitSync 的工作原理 - 同时执行更改:冲突

在交互结束时,两个存储库将看起来相同,并允许开发者在两端协同工作。

注意:由于 Plastic SCM 是一个完整的 DVCS(像 Git 一样),因此它可以克隆 Git 存储库,然后将更改完全推送到这个存储库!我们不限于单个分支。您可以在 Plastic 上创建分支,并将这些分支推送到 Git。还可以在 Git 上创建分支,并将这些分支拉取到 Plastic。

gitsync.conf 文件

GitSync 配置文件 (gitsync.conf) 允许您包含将在 GitSync 操作期间自动使用的信息。

这些信息与两个 Plastic SCM 和 Git 对象之间的映射有关:

  • 用户帐户
  • Xlink/子模块信息
gitsync.conf 文件必须位于:
  • Plastic SCM 客户端文件夹中。
  • plastic4 目录中(Linux/Mac 系统上的 $HOME/.plastic4 下或 Windows 上的 C:\Users\user\AppData\Local\plastic4 下)。

映射用户帐户

gitsync.conf 文件中,我们可以定义 Plastic 和 Git(电子邮件地址)之间的映射。提交到 Git 时,此信息将用作作者和提交者。

此信息用以下格式添加到 email-mapping 部分中:

plastic_user = git_email_address

以下是 gitsync.conf 文件的一个示例:

[email-mapping]
asalim = asalim@mymailprovider.com
ubuntu = ubuntu@linuxprovider.com

映射 Plastic SCM Xlink 和 Git 子模块


GitSync 限制

使用 GitSync 时,有两个 Git 操作受到限制:

  • rebase 命令
  • merge (fast-forward) 命令

这些 Git 命令不会考虑您的更改历史记录。尽管用户可以并行工作,Git 展示的历史记录却是接近线性的。但是,Plastic 会优先考虑保留更改历史记录。

Plastic SCM 会考虑您所做的更改的完整历史记录。这意味着 Plastic 不会改写历史记录。我们并不反对这种方案,这是一项设计决策,一种哲学。

在处理分支和合并时会反映出更改历史记录。由于 Plastic SCM 能够解决并显示历史记录,我们建议您在使用 GitSync 时避免使用这些 Git 命令。

  • 在 Git 中,变基是一个微妙的话题;变基只能在推送之前执行(事实上,很多人建议永远不要使用变基),而且大多数时候可用来了解历史记录,不会让人因为所有这些合并而抓狂:
    GitSync 限制 - Git rebase 命令
    我们不以 Git 方式处理变基。
  • Git fast-forward merge 命令具有类似的功能,以线性方式解决合并:
    GitSync 限制 - Git fast-forward merge 命令
    使用 merge --no-ff 命令可保留历史记录。

Plastic 具有分支资源管理器,可让您以图形方式了解正在发生的情况。这样就不会分心,也不会错过变基。

对具有多个合并的分支进行差异比较时,很难分辨出在分支上真正进行的更改,以及合并带来了什么... 这一问题也在 Plastic 中得到了解决

GitSync - Plastic 合并
为避免在同步 Plastic-Git 存储库时出现意外结果,我们建议不要使用 Git rebasemerge (fast-forward) 命令。

一般来说,不要使用那些会改写存储库历史记录的命令。例如,删除或移动变更集,删除或移动标签...

直接推送/拉取

正如我们之前了解到的,Plastic 可以使用原生 Git 和 https 协议直接进行与远程 Git 服务器之间的推送和拉取操作,其中包括 GitHub、BitKeeper、CodePlex 等知名站点。

当我们最初开发 Plastic SCM 与 Git 双向同步功能时,我们考虑了以下场景:

  • 已在使用 Plastic SCM 的开发者希望参与 GitHub、CodePlex、BitKeeper 等站点中的项目。
  • 在使用 Git 作为主服务器的团队中,开发者更喜欢使用 Plastic SCM,但需要将更改同步回主服务器。
  • 团队逐步采用 Plastic SCM,并需要在 Git 上与其他团队合作。

我们克服万难找到了解决方案:我们没有考虑使用某种中间脚本将更改从一个系统转换到另一个系统,也没有考虑进行快速导入/导出(这种方案会施加大量限制),而是选择将 Git 网络协议实现为 Plastic 中的一个层,这个层支持与 Git 之间的直接拉取和推送。

Plastic 使用 Git 协议启动与远程 Git 服务器的协商阶段,就像 Git 命令所做的那样。这是一个核心功能,而不是附加脚本。

正如我们所说,GitSync 实现了智能协议并且:

  • 可与远程 Git 接收包协商以上传数据(协商需要哪些变更集/提交,并从 Plastic 存储库数据生成正确的包文件以发送到 Git)。
  • 还可与远程上传包协商以决定需要打包哪些提交,下载这个包,并将这个包导入到 Plastic 中。

首次拉取

让我们首先连接到 GitHub 存储库。

  1. 如果您转到 GitHub 并浏览存储库,可能会找到类似于“trendy”存储库列表的内容。在示例图中,我选择了其中一个存储库,结果是 corefx 存储库:
    GitSync - 首次拉取 - 选择 GitHub 存储库

    现在,为了将该存储库拉取到 Plastic,我创建了一个存储库来“托管它”(也命名为 corefx)并转到最初为空的分支资源管理器,然后转到上下文菜单选项以启动与 Git 同步

    GitSync - 首次拉取 - 与 Git 同步
  2. 然后,您需要启动“同步”对话框(这与在 Plastic 服务器之间推送/拉取更改的复制对话框非常相似),并输入 Git 存储库的 URL:
    GitSync - 首次拉取 -“同步”对话框

    现在不需要凭据,因为我们只是从公共存储库进行拉取(克隆)。如果您需要推送,然后服务器会要求您是经过身份验证的用户,那么您就需要指定凭据。

  3. 只需单击同步,该过程(拉取)就会启动,如下所示:
    GitSync - 首次拉取 - 同步
    注意:可以通过以下方式使用命令行来完成此操作: cm sync corefx git https://github.com/corefx/corefx.git

    假设本地 corefx 存储库是空的,它将计算需要从远程 GitHub 存储库拉取的变更集和分支,并将拉取它们:

    GitSync - 首次拉取 - 同步命令行

    为了拉取在 GitHub 端完成的新更改,您只需重新运行相同的命令:

    cm sync corefx git https://github.com/corefx/corefx.git

    现在它只会计算和拉取在 Git 端所做的新更改(如果有)。

    您目前正在将 Git 变更集和分支直接拉取到您的本地 Plastic SCM 存储库:

    GitSync - 首次拉取 - 拉取
    GitSync - 首次拉取 - 拉取摘要
  4. 复制完成后,我们将返回到工作区资源管理器,然后我们将更新我们的工作区以下载源文件。
  5. 也刷新分支资源管理器。您将能够以典型的 Plastic SCM 方式呈现刚刚导入的 Git 变更集:
    GitSync - 首次拉取 - 分支资源管理器

    现在,只需右键单击任何变更集(Git 中的说法是提交),您将能够使用我们的内置差异比较系统查看差异:

    GitSync - 首次拉取 - 差异比较

现在您已准备好在 Plastic 中进行更多更改,无论是分支、合并还是任何其他操作。然后,重复相同的过程以同步到 Git(这将依次推送或拉取更改,如果并发更改是在同一分支上完成的,甚至会要求您在推送回 Git 之前解决合并问题)。


推送到 Git

我们将把我们的 Plastic 存储库之一推送到 GitHub。您会看到此过程与前一个过程类似。

好的!让我们开始吧!

  1. 我创建了一个新的 GitHub 存储库,以便将我的 Plastic 存储库导出到 GitHub:
    GitSync - 首次推送 - 创建 GitHub 存储库
  2. 我选择了我的 dokancode Plastic 存储库。在“分支资源管理器”视图中,右键单击它的一个分支,然后像我们在上一章中所做的那样,选择与 Git 同步菜单选项:
    GitSync - 首次推送 - 与 Git 同步

    随即将启动“同步”对话框。

  3. 然后,根据需要输入 GitHub 存储库 URL 和凭据:
    GitSync - 首次推送 -“同步”对话框
  4. 然后,单击同步按钮以开始同步。在本示例中,我们将推送(或导出)我们的 Plastic 存储库:
    GitSync - 首次推送 - 同步

    我们目前正在将 Plastic SCM 变更集和分支直接推送到我的 GitHub 存储库。

    注意:可以通过以下方式使用命令行来完成此操作: cm sync dokancode git https://github.com/mbctesting/dokancode.git

    假设 GitHub dokancode 存储库是空的,GitSync 将计算它需要从 Plastic 存储库推送的变更集和分支,并将推送它们:

    GitSync - 首次推送 - 同步命令行

    推送操作完成后,我们可以看到已导出对象的摘要:

    GitSync - 首次推送 - 推送
    GitSync - 首次推送 - 推送摘要
  5. 如果我们返回 GitHub 并刷新 dokancode 存储库,我们将看到从 Plastic 导出的对象:
    GitSync - 首次推送 - GitHub

现在我们已准备好使用 Plastic SCM 和 GitHub 这两端的存储库。我们将能够创建分支,进行更改... 以及通过拉取/推送再次同步。


在两端工作

在本章中,我们将向您展示如何在 GitHub 和 Plastic 端使用 dokancode 存储库,并应用一些基本操作。

这是推荐的步骤 - 在任一端执行任何更改之前,您必须同步自己的存储库(使用与 Git 同步操作)以避免冲突。

Git 端的更改

现在我要在 master 分支上运行一些操作:

  1. 删除 license.txt 文件:
    GitSync - Git 端 - 删除文件
  2. 编辑 dokan-net-0.6.0\readme_dokan.txt 文件:
    GitSync - Git 端 - 编辑文件
  3. 然后将该文件移动到 dokan-net-0.6.0\DokanNet 文件夹中:
    GitSync - Git 端 - 移动文件
  4. 接下来,我将创建一个新分支 (scm007)。
    GitSync - Git 端 - 创建新分支
  5. 添加两个新文件:
    GitSync - Git 端 - 添加新文件
    GitSync - Git 端 - 添加新文件
  6. 编辑其中一个文件:
    GitSync - Git 端 - 编辑新文件

提交已经完成,现在我们准备好了在 Plastic 端查看它们。

我们在本指南中已经了解 GitSync 能够计算出另一端的更改,与远程服务器协商,并推送更改。

  1. 因此,让我们通过运行与 Git 同步操作来将 Plastic 存储库与 GitHub 存储库同步:
    GitSync - Git 端 - 与 Git 同步
    GitSync - Git 端 - 同步
  2. 通过单击同步,同步将开始并会将我们在 GitHub 端执行的更改拉取到 Plastic 存储库:
    GitSync - Git 端 - 同步

    同步完成后:

    GitSync - Git 端 - 已完成同步
  3. 我们可以看到已导入对象的摘要:
    GitSync - Git 端 - 同步摘要
  4. 如果我们返回分支资源管理器并刷新视图,我们将看到在 GitHub 端所做的更改已导入到 Plastic:
    GitSync - Git 端 - 分支资源管理器
  5. 我们可以打开变更集视图来查看这些 GitHub 更改:
    GitSync - Git 端 - 变更集视图
  6. 此外,如果想要更深入了解,我们可以通过 GitHub 端的 readme_dokan.txt 文件确认所做的更改。我们可以查看该文件的历史记录以检查是否已通过同步应用这些更改:
    GitSync - Git 端 - 更改

现在我们可以继续在 Plastic 端执行更改。


Plastic 端的更改

我将在 scm005 分支上执行一些更改。此时,Git 端和 Plastic 端的内容相同。我们可以在两端检查这一点:

GitSync - Plastic 端
GitSync - Git 端

在此分支中,我将:

  1. 添加新文件:
    GitSync - Plastic 端 - 添加新文件
  2. 编辑 DokanOperation.cs 文件:
    GitSync - Plastic 端 - 编辑文件

    新的更改如下:

    GitSync - Plastic 端 - 新更改
  3. 更改完成后,我将通过运行与 Git 同步操作来同步 GitHub 存储库:
    GitSync - Plastic 端 - 与 Git 同步
  4. 同步完成后,我们可以看到摘要:
    GitSync - Plastic 端 - 同步摘要

    通过摘要可以知道,同步过程发送了一个分支中涉及的两个变更集:

    GitSync - Plastic 端 - 变更集
  5. 如果我们转到 GitHub 端,我们可以看到在 Plastic 端进行的这些新更改:
    GitSync - Plastic 端 - GitHub

Plastic 和 Git 分支转换

如果您注意观察,就会发现 Plastic main 分支已映射到 Git master 分支。Plastic 上 main 分支的子分支也可能存在此类映射。这意味着会通过删除层次结构并将 / 替换为 -,将 Plastic 分支转换为 Git 分支。同样,当 Git 分支转换为 Plastic 分支时,此规则也有效:- 字符用于在 Plastic 中重新创建层次结构。

在前面的示例中,Plastic 分支 /main/scm005 将转换为 master-scm005。此外,master 下的 Git 分支 scm007 将转换为 /main/scm007

GitSync - Plastic 和 Git 层次结构

- 字符也可以作为分支名称的一部分,如以下这些示例所示:

分支 - Git 端 分支 - Plastic 端
master /main
master-fix-5.0 /main/fix-5.0
master-fix-5.0-task1 /main/fix-5.0/task1

完整合并跟踪

由于 Plastic SCM 采用与 Git 相同的概念(DAG、提交、合并链接等),因此共享合并跟踪相当容易。可以在 Git 中进行合并,然后推送回 Plastic 中。在 Plastic 中进行合并也没有问题,甚至可以将合并链接推送回 Git 中。

我们最难处理的功能与“项的精准跟踪”相关;我们有这项功能,而 Git 没有。Plastic 有一个与每个文件和目录相关联的内部 ID。这意味着我们可以轻松处理大量 Git 难以跟踪的合并冲突(例如分歧移动,这对于 Plastic 来说易如反掌)。

在以下示例中,我们将看到使用 GitSync 时是如何处理合并跟踪的。


Plastic 端的合并

我们将创建两个新分支:

  1. scm008 分支 - 我将编辑 DokanNet.cs 文件中的 DokanResetTimeout 方法,将该方法移动到文件中,并添加新的字段:
    GitSync - Plastic 端 - 合并 - 编辑文件
  2. scm009 分支 - 我将把 DokanNet.cs 文件移动到另一个文件夹中,然后我将把 DokanResetTimeout 方法移动到另一个类中并编辑该方法:
    GitSync - Plastic 端 - 合并 - 移动文件
  3. 在不同的分支上完成更改后,我会将它们合并到 main 分支,然后得到以下结果:
    GitSync - Plastic 端 - 合并
    GitSync - Plastic 端 - 合并结果
  4. 现在我将使用您已了解的与 Git 同步操作来同步两端的存储库。这样,我将在 Git 端推送这些合并。

新分支已被推送,Git 端的合并跟踪如下所示:

GitSync - Plastic 端 - 已推送到 Git

Git 端的合并

现在我们将了解在 Plastic 端跟踪 Git 端的合并。

  1. 我将创建一个新分支,然后我将创建一个新文件并编辑另一个文件:
    GitSync - Git 端 - 合并 - 更改
    GitSync - Git 端 - 合并 - 更改
  2. 提交完成后,我将执行从 scm010 分支到 master 分支的合并:
    GitSync - Git 端 - 合并
    请注意,合并是使用 --no-ff 选项完成的,正如我们在 GitSync 限制一节中看到的那样。
    GitSync - Git 端 - 合并完成

    合并完成,现在我们要将这些更改拉取到远程 Git 存储库:

    GitSync - Git 端 - 合并 - 更改
  3. 将本地 Git 更改拉取到远程 Git 存储库后,我们便可以开始同步我们的存储库。这意味着最新的更改将被拉取到 Plastic 存储库。如果我们运行与 Git 同步操作:
    GitSync - Git 端 - 合并 - 与 Git 同步
  4. ...并在同步完成后刷新分支资源管理器,我们将看到 Git 端所做的更改和合并如何被拉取到 Plastic 端:
    GitSync - Git 端 - 合并 - 分支资源管理器

冲突管理

我们在工作原理一章中了解到,我们可以同时在 Plastic 和 Git 端进行更改。这意味着您可以处理这两个系统上的同一分支,并协调更改(操作方式与使用纯 Plastic 环境或纯 Git 环境类似)。

我们将了解如何使用 GitSync 和 Plastic 管理这种情况。

让我们首先回顾一下前面的一个示例。在 Git 端的更改一节中,我们在 GitHub 端进行了一些更改。以下是我所做的操作:

  1. master 分支中,我删除了 license.txt 文件,编辑了 dokan-net-0.6.0\readme_dokan.txt 文件,并将其移到了 dokan-net-0.6.0\DokanNet 文件夹中。
  2. 然后我创建了一个新分支 (scm007),在其中,我添加了两个新文件(ArrayIndex.csArrayInitialization.cs)并编辑了其中一个文件 (ArrayIndex.cs)。

现在,我将在 Plastic 端执行一些更改:

  1. 在 main 分支中,我将编辑 DokanNet.cs 文件:
    GitSync - 冲突管理 - Plastic 端 - 编辑文件
  2. 然后添加 3 个新文件:
    GitSync - 冲突管理 - Plastic 端 - 添加文件

更改完成后,我决定使用与 Git 同步操作来同步两个存储库:

GitSync - 冲突管理 - 与 Git 同步
GitSync - 冲突管理 - 与 Git 同步

同步完成后,有一条消息会告诉我们需要合并!

GitSync - 冲突管理 - 需要合并

这是因为我们在 Git 和 Plastic 端的同一分支中都进行了一些更改。

在摘要中,我们将看到需要合并操作的分支是 main(或 master)分支:

GitSync - 冲突管理 - 需要合并 - 摘要

这是事实:我们在 Git 端的 master 分支中应用了一些更改,在 Plastic 端的 main 分支中应用了另外一些更改。请注意,mainmaster 是同一个分支,但使用不同的名称(有关更多信息,请参阅 Git-Plastic 字典)。

工作原理一节中所述,我们必须解决 Plastic 端的合并冲突。所以让我们来完成这一操作吧!

  1. 如果我们更新分支资源管理器,我们将看到:
    • 首先,main 分支有多个必须合并的“头部”。这是因为这些冲突在 Plastic SCM 中作为“子分支”处理。
    • 其次,scm007 分支已经同步(从 GitHub 拉取到 Plastic SCM)。
    GitSync - 冲突管理 - 需要合并 - 分支资源管理器
  2. 为了解决此冲突,我们将从“GitHub”头部(子分支的头部)运行合并:
    GitSync - 冲突管理 - 需要合并 - 从 GitHub 头部合并

    我们将看到即将合并的更改:

    GitSync - 冲突管理 - 需要合并 - 要合并的更改
  3. 单击处理所有合并按钮后便会自动合并这些项。
  4. 最后一步(先前介绍过)是在待定更改视图中签入(确认)更改:
    GitSync - 冲突管理 - 需要合并 - 签入
  5. 单击签入按钮后,您将看到两个“头部”合并为单个头部,即 main 分支中的头部:
    GitSync - 冲突管理 - 已解决合并
  6. 合并冲突现在已解决。但是,我们需要通过在 Git 端推送在 Plastic 端所做的更改来完成同步。您可能已经知道,使用与 Git 同步操作即可完成同步。

此操作完成后,两个存储库将完全同步。这意味着我们在这两端具有相同的内容。


SSH 协议支持

GitSync 支持使用 SSH 协议与 Git 存储库同步。

SSH 协议允许您连接到远程服务器和服务并进行身份验证。


先决条件

要使用此功能,必须满足以下要求:

  1. 您的 PATH 环境变量中必须有命令行 SSH 客户端 ssh
  2. 必须将您的私有 SSH 密钥添加到您的 ssh-agent。请按照这些说明将您的 SSH 密钥添加到 ssh-agent。

    SSH 代理现在会管理您的 SSH 密钥并记住您的密码短语。


如何使用

现在可以使用 Plastic GUI 或 CLI,按照与通常使用 HTTP 协议相同的方式使用 GitSync。

让我们看一个示例。如果使用命令行,则必须相应地为 SSH 协议指定 URL:

$ cm sync rep2 git git@github.com:PlasticSCM/Myrepo.git

而不是使用 HTTPS 版本:

$ cm sync rep2 git https://github.com/PlasticSCM/Myrepo.git

上次更新

2020 年 9 月 24 日
2020 年 5 月 26 日
2017 年 6 月 16 日
  • 了解 Plastic 和 Git 之间(或反之)如何进行分支转换。我们还更新了相关的屏幕截图。
2016 年 4 月 12 日
  • 发布日期。