系列文章:Oracle ADF 开发必读 — 第 2 部分


借助 Subversion 进行版本控制:在团队环境下管理版本


了解 Subversion 如何帮助您在团队环境下管理版本。

作者:John Stegeman

2008 年 12 月发布

下载:

在本系列的第 1 部分,您已学习了安装 Subversion 、设置信息库和使用 Oracle JDeveloper 11g 执行基本操作等基础知识。在第 2 部分中,您将了解当您将另一位开发人员加入到团队中后,Oracle JDeveloper 和 Subversion 将如何工作。

在团队中加入新的开发人员

如果您从第 1 部分就按照教程进行操作,您一定记得我们创建了两个可以访问 Subversion 信息库的用户 john 和 josephine。到目前为止,John 一直是此应用程序唯一的开发人员;我们来看看 Josephine 如何加入到团队中并对该应用程序进行操作。

Josephine 要做的第一件事是从信息库签出一个应用程序的副本。记住“签出”应用程序副本只是在本地设备上放入一个应用程序的本地副本(以及一些日常管理目录)。这样,您就能知道本文中都是谁在执行操作,我将 Josephine 作为本文的主角。

为签出应用程序的副本,Josephine 从 JDeveloper 的 Versioning 菜单中选择 Check Out…

图 1

图 1 签出应用程序副本

因为 Josephine 没有定义信息库连接,所以她填写了信息库信息、提供了她的凭证并测试了连接:

图 2

图 2 创建 Subversion 连接

因为她希望在代码的开发主线(“trunk”)上工作,所以她选择了 trunk 目录并为签出副本提供了她本地硬盘驱动器上的目标路径:

图 3

图 3 指定签出文件的位置

然后,Oracle JDeveloper 从信息库中签出应用程序代码的最新版本并将其打开。Josephine 现在可以对应用程序代码进行操作了。

为项目添加新文件

现在,Josephine 是团队的一员,她希望将一个新的 Java 源代码文件添加到 ViewController 项目中。因为团队使用的是 Subversion,所以她不需要做任何特别的事情来创建新的 .java 文件并将其添加到她的项目中;只需像往常一样在 Oracle JDeveloper 中工作。Josephine 创建了一个新的 .java 文件并在其中创建了一些方法:

图 4

图 4 新 Hello.java 文件

当 Josephine 在 Application Navigator 窗口中看到新文件时,她将看到她的文件上有一个蓝色的 x 图标,这表示该文件还没有与 Subversion 信息库相关联:

图 5

图 5 Hello.java 显示蓝色 x 覆盖图标

而且,当她查看 Pending Changes 窗口时(Oracle JDeveloper 菜单中 Versioning -> Subversion -> Pending Changes),她将看到她的新 Hello.java 文件在 Candidates 选项卡中列出,这表示她有一个文件待添加到版本控制中:

图 6

图 6 Pending changes 显示 Hello.java 为待添加项目

为了将新文件添加到版本控制,她选择该文件并使用 Pending Changes 窗口中的“add”按钮(加号 [+] 图标):

图 7

图 7 通过 Pending Changes 窗口添加待选文件

或者在 Application Navigator 窗口中右键单击该文件并选择 Add...

图 8

图 8 通过 Application Navigator 上下文菜单添加待选文件

两种方法的结果都一样 — 使用 Oracle JDeveloper 中的一项功能经常会有不止一种方法。添加文件将会对其进行标记以添加到信息库中;注意添加文件 会将更改提交到信息库中。当 Josephine 查看 Pending Changes 窗口中的 Outgoing 选项卡时,她或许会感到一点惊讶,因为显示她有五个传出更改,而不是仅一个:

图 9

图 9 Pending Changes 窗口显示需要添加的文件和目录

原因是 Subversion 不仅对目录,而且对文件也可以进行版本控制;添加一个新的 Java 文件也创建了一个目录结构。现在,Josephine 已完成更改,她已经准备好将它们提交到信息库中了;在她这么做之前,她的更改只是存储在她的本地硬盘驱动器中而不能供团队中的其他人使用。在第 1 部分,John 使用 Oracle JDeveloper 中的 Commit Working Copy… 操作提交了他的更改;Josephine 的做法将会有一点不同。她选择了 Pending Changes 窗口中的所有项目(一个文件和四个目录)并单击 Commit… 按钮:

图 10

图 10 向信息库提交更改

然后,她提供了提交消息/注释以便其他人知道她做出了什么修改:

图 11

图 11 为更改提供注释

然后,Oracle JDeveloper 会将更改发送到信息库,Subversion 将保存它们并递增修订号。

从信息库更新应用程序

因为 John 的工位就在 Josephine 的旁边,所以他可以和她讨论项目并得知她向项目中添加了一个新的 Java 类。他回到自己工位,在 Oracle JDeveloper 中打开他的项目副本,可在任何地方都没能找到该新文件:

图 12

图 12 John 的应用程序(Hello.java 在哪里?)

为什么 John 看不到它?因为每个开发人员(John 和 Josephine)都在对应用程序的本地副本进行操作。当另一个团队成员提交更改时,更改 会自动从信息库复制到本地工作副本;更改仅在开发人员特别要求从信息库更新 他或她的工作副本时才会进行复制。

通常,开发人员的最佳做法是定期在开始新工作前更新他们的工作副本。然而,如果不更新,开发人员怎么知道应用程序有更改需要更新呢?让我们回到 John 的工作会话以进行演示。当 John 查看 Pending Changes 窗口时,他注意到在 Incoming 选项卡中有新的文件:

图 13

图 13 Pending Changes 窗口中显示传入更改

通常,John 可以通过在 Pending Changes 窗口中选择要传入的更改并单击 Update 按钮来更新他的工作副本。但是,由于一些原因,在他的 Oracle JDeveloper 11g 副本中,该按钮不可用,所以 John 使用了 Application Navigator 窗口中的 Update Working Copy...

图 14

图 14 更新工作副本

在显示的对话框中,他接受了默认值并单击 OK

图 15

图 15 Update Working Copy 对话框

Oracle JDeveloper 从信息库更新应用程序后,John 现在可以在 Application Navigator 窗口中看到 Josephine 新添加的文件:

图 16

图 16 John 的应用程序显示 Hello.java

最佳实践:确定公用代码样式设置

John 打开 Hello.java 查看 Josephine 都做了什么。他首先注意到代码格式的样式和他的不一样;相对于左大括号在同一行的格式,

图 17

图 17 Josephine 的代码样式

他更喜欢左大括号在下一行的样式:

图 18

图 18 John 的代码样式

所以他做的第一件事是使用 Oracle JDeveloper 中的 Reformat 命令让文件更易于阅读。然而,当他这样做之后,文件的内容就被更改了,并且产生了一个需要提交给信息库的更改。如果他提交更改,当 Josephine(她更喜欢原来的代码样式)下一次更新她的工作副本时,会将代码格式重新设置为她喜欢的样式。如果他们不协商出一个公用的样式,John 和 Josephine 将永远在 Java 代码上打乒乓,无限次地重新格式化另一个人的代码。

这个案例的教训是,当在团队环境下使用版本控制时,您需要预先就公用标准进行协商以避免出现这样的问题。幸运的是,Oracle JDeveloper 具有一个用于指定代码样式首选项的选项:

图 19

图 19 在 Oracle JDeveloper 中设置代码样式首选项

John 喜欢的样式是 JDeveloper Classic,而 Josephine 喜欢的样式是 Java Code Conventions。John 和 Josephine 进行了短暂的讨论,同意都使用 JDeveloper Classic 设置。John 向 Subversion 提交了他对 Hello.java 更改(并提供了关于他所做更改的注释):

图 20

图 20 John 的更改注释

Josephine 更改她的代码样式首选项,看到 Pending Changes 窗口中 Incoming 选项卡中的更改,并将更改更新到她的工作副本:

图 21

图 21 Josephine 将 John 的更改更新到她的工作副本

解决冲突

如前文所述,开发人员的工作副本只是其本地硬盘驱动器上的一个本地应用程序副本,所以才有了两个开发人员同时对一个文件进行操作的可能性。Subversion 提供了处理这种情况的机制。有两个基本用例:第一个,如果一个开发人员在文件的开始部分对某些内容进行了编辑,而另一个开发人员在同一文件的结尾部分对某些内容进行了编辑,Subversion 可以合并这些更改。Subversion 通过一个简单的文本比较算法来完成合并,所以它不能确保更改是兼容的,但可以通过修改相同的行来确保更改没有彼此冲突。在另一个用例中,如果两个开发人员都编辑了同一个文件的同一行,Subversion 就不能合并这两个更改而需要用户手动去解决冲突。幸运的是,Oracle JDeveloper 11g 提供了一个绝妙的可视冲突解决机制。

我们来看第一个用例,以 John 和 Josephine 的 Hello.java 文件为例。首先,John 对 sayHello 方法做了一个简单的更改以让它使用更随意的英语:

图 22

图 22 John 对 Hello.java 的“非正式英语”更改

同时,Josephine 对 sayGoodbye 方法进行了修改以使它使用斯瓦希里语:

图 23

图 23 Josephine 对 Hello.java 的斯瓦希里语更改

之后,Josephine 向 Subversion 提交了她的更改:

图 24

图 24 Josephine 提交她的斯瓦希里语更改

现在,有趣的是 — 当 John 尝试提交他的更改时,他收到了以下错误消息:

图 25

图 25 错误信息提示 John 他的 Hello.java 副本已过期

Subversion 想要告诉他的是信息库的 Hello.java 版本已与他上次更新的版本不同,他必须先更新文件然后才能提交更改。所以 John 更新了他的工作副本,更新之后的 Hello.java 如下:

图 26

图 26 Josephine 的更改合并到 John 的工作副本中的 Hello.java

注意,Subversion 已经自动将 Josephine 的更改合并到 John 的工作副本中。Subversion 能告诉我们更改合并后会不会仍有效(一个更改使用非正式英语而另一个更改使用斯瓦希里语);它们放在一起是否有效由 John 来决定。我们假设 John 认为这两个更改还不错;要记住目前更改还仅在 John 的工作副本中而不在信息库中。所以 John 提交了修订后的 Hello.java,并仅对他的更改做了注释:

图 27

图 27 John 提交合并后的 Hello.java 文件

最后,当 Josephine 更新她的工作副本时,她将得到一个包含她和 John 两个人的更改的 Hello.java 的最新版本。

现在让我们看一下,当两个人对同一文件做出了更改而 Subversion 不能自动合并更改时会发生什么。Josephine 对 Hello.java 进行的编辑如下:

图 28

图 28 Josephine 对 Hello.java 做出了一些大的更改

同时,John 对他的工作副本的编辑如下:

图 29

图 29 John 也对 Hello.java 做出了一些大的更改

这次 John 先提交了他的更改。现在,如果 Josephine 尝试提交 Hello.java,她将获得相同的提示她本地副本过期的错误消息。然而,当她尝试更新她的工作副本时,她将注意到三个问题。首先,在 SVN Console - Log 窗口中,她将看到一个冲突的提示:

图 30

图 30 提交时日志中显示的冲突

其次,在 Pending Changes 窗口中,她的文件副本将标记为有冲突:

图 31

图 31 Pending Changes 窗口显示文件有冲突

最后,在 Structure 窗口中,她将看到几个额外的 Hello.java 副本:

图 32

图 32 Structure 窗口显示 Hello.java 的其他版本

这些文件是 Subversion 创建用来帮助解决冲突的,但是不必手动解决冲突,Oracle JDeveloper 提供了一个非常好的可视合并工具,Josephine 可以通过右键单击 Hello.java 并选择 Resolve Conflicts 来激活该工具:

图 33

图 33 启动可视合并工具

可视合并工具在左侧显示 Josephine 的文件版本(名为 Hello.java.mine),在右侧显示来自信息库的当前版本(名为 Hello.java.r<修订号>),在中间显示合并结果:

图 34

图 34 可视合并工具显示 Hello.java 的三个版本

有冲突的更改清晰地标为红色。为了解决冲突,Josephine 只需选择每行应该在最终文件中出现的版本,并通过单击相应的绿色箭头(> 或 <)将其合并到中心窗格中。例如,我们假设 Josephine 选择了她的版本的 sayHello 和 John 版本的 sayGoodbye。结果如下:

图 35

图 35 解决冲突后的可视合并工具

现在所有的冲突都解决了,她可以单击 Save and Complete Merge 按钮完成操作。当她刷新 Application Navigator 窗口后,所有的冲突构件(Hello.java 的额外版本)都将删除,她可以用正常的方式向信息库提交她的更改。John 现在可以更新他的工作副本,并将看到文件的新版本。

Praise 和 Blame 命令

Subversion 命令行程序有两个名字很奇怪的命令 praise 和 blame,可以用来识别最后提交对文件每一行的更改的人。praise 和 blame 命令的作用是相同的;通常,当您想要找出谁做了有趣或正确的更改时,可以使用 praise,而当您要找出谁引入了一个错误时,可以使用 blame。

Oracle JDeveloper 通过“批注”可以采用可视的方式公开这一信息。要查看赞扬/批评信息,只需右键单击编辑器,并在 Versioning 子菜单中选择 View Annotation

图 36

图 36 显示批注

Oracle JDeveloper 将在左侧边列显示彩色条;当您将光标移动到它们上方时,Oracle JDeveloper 将会显示以下信息:最后提交对该部分更改的开发人员、提交的修订号以及提交日期:

图 37

图 37 批注显示 John 修改了一部分代码

添加标记

现在您已经了解了如何使用 Subversion 的基础知识,下面我们来看另一个常用活动:“添加标记”。添加标记是用于在特定时间点标注或标记源代码副本的过程。按照惯例,已添加标记的副本不会再次更新和提交;它就是一个时间点参考,可以让您轻松获得在特定时间点上存在过的代码副本。当将应用程序的一个版本发布到生产中时,会经常使用添加标记,这样您可以轻松地找到已部署的代码版本。

回到由 John 和 Josephine 组成的演示团队,在做了修改后,他们已经准备好将其代码发布到生产中,并且决定将第一个版本称为版本 1.0。作为其发布的一部分,他们希望标记代码的版本为 v1.0,这样,他们可以清楚地知道哪一个版本的代码已发布到生产中,并且可以随时签出代码的 1.0 版进行调试。Subversion 中的标记就是一个代码的副本,按照惯例,放在“tags”目录中。为创建标记,Josephine 在 Versioning 菜单中选择 Branch/Tag…

图 38

图 38 选择分支/标记

Oracle JDeveloper 然后显示一个对话框,在这里,Josephine 可以提供粘贴和复制的信息库详细位置。Josephine 希望标记是其当前工作副本的代码,所以她保留了复制源的默认设置 Working Copy。为指定目标位置,她在信息库中指定了一个新的标记子目录:

图 39

图 39 为标记指定位置

如果选中 Switch to new branch/tag 复选框,会将 Josephine 的工作副本指向标记而不是指向主干;因为惯例是永远不更新标记,Josephine 希望将她的工作副本继续指向主干以便她能继续工作,所以她没有选中 Switch to new branch/tag

在 Josephine 单击 OK 后,她的工作副本被复制到 v1.0 标记目录。如果她使用 Web 浏览器查看信息库,将看到一个标记为 v1.0 的代码副本:

图 40

图 40 Subversion 信息库中新创建的标记

以后的任何时候,开发人员可以轻松地使用标记的 URL 作为签出地址签出版本 1.0 的代码副本。关于 Subversion 值得注意的一点是,这样的代码副本几乎不占用空间!Subversion 只存储一个到复制源的指针;副本不更新,它就几乎不占用空间。

分支和合并

当开发团队需要创建一个代码分支以独立于主干进行更改时,就引出了另一个 Subversion 常见用例。常见的创建分支的情景是当团队尝试稳定代码为生产发布做好准备时;团队的部分成员负责查找和修复错误,而团队的其余成员则继续为下一版本开发新特性。稳定团队需要拥有其独立于主干的错误修复代码分支(将包括还不能用于生产的新特性)。当稳定团队找到错误并修复时,还需要将这些错误修复传输回主干代码。当分支代码可用于生产时,将对其进行标记并部署到生产中。要了解分支的其他常见用例,请参阅借助 Subversion 进行版本控制电子书。

我们来演示“针对产品发布的稳定”用例,其中将用到 Oracle JDeveloper、Subversion 以及我们熟悉的 John-and-Josephine 开发团队。John 将负责准备应用程序的下一个生产发布(版本 1.1),而 Josephine 将继续为版本 2.0 开发新特性。John 首先要做的是创建一个名为 stabilization_1.1 的分支。为此,他使用了进行标记时使用的 Branch/Tag… 命令。这里和标记用例主要有两点不同:首先,按照惯例,分支放在信息库的 Branches 子目录下。其次,因为 John 想要对分支做出更改,所以他要将他的工作副本改为指向分支。所以,John 选择 Branch/Tag… 并按照如下所示填写对话框:

图 41

图 41 Branch/Tag 对话框显示分支的创建

在 John 单击 OK 后,分支即在信息库中创建,并且 John 的本地工作副本改为指向新的分支。注意,分支只是代码的一个副本 — 团队中的任何其他成员只需指定相应的 URL 即可签出分支的工作副本。值得注意的是,任何人都可以同时在其本地系统上存有多个工作副本;例如,John 也可以签出一个主干的副本(放入一个新的目录),他将有一个指向分支的工作副本,还将有一个指向主干的工作副本。

同时,Josephine 结束了在主干上为版本 2.0 添加新特性的工作。在这里,我将不会显示她的工作,但是她进行了两组更改并提交给 Model 项目。John 在 Pending Changes 窗口中没有看到任何 Josephine 的更改,因为他的工作副本指向 stabilization_1.1 分支,而 Josephine 是在主干中做出的更改。现在,John 对 Hello.java 文件做了一些修复,在返回信息中添加标点:

图 42

图 42 John 在稳定分支中对 Hello.java 所做的更改

当 John 提交他对分支的更改时,注意其更改的版本号是很重要的,这样他才能将更改合并回主干。Subversion 1.5 支持自动跟踪合并,但是 Oracle JDeveloper 11g 不支持 Subversion 1.5 客户端,所以 John 必须手动跟踪合并。John 提交他的工作副本并在 SVN Console - Log 窗口中看到新的修订号 (17):

图 43

图 43 Log 显示修订号 17

注意,Josephine 没有看到 John 的更改,因为更改发生在分支里,而 Josephine 在主干上工作。John 如果要将更改合并到主干中,则需要将一个工作副本指定到主干。因为 Josephine 的工作副本已经指向主干,所以我们将她的副本和 John 的更改合并。John 告诉 Josephine 分支中的修订 17 需要合并到主干中。Josephine 然后在 Versioning 菜单中选择 Merge…

图 44

图 44 选择 Merge

Merge 对话框可能会有一点混乱;Merge… 实际做的只是确定信息库的两个不同部分(或修订)间的差异并将差异应用到工作副本。所以,要将 John 的更改(修订 17)合并到主干中,Josephine 需要 Subversion 确定分支中修订 17 和修订 16 (之前修订)的差异并将这些差异应用到她的指向主干的工作副本中。因此,她按如下所示填写了 Merge 对话框:

图 45

图 45 在 Merge 对话框中指定选项

当她单击 Test Merge 按钮时,Oracle JDeveloper 将显示她的工作副本即将做出的更改(U 意味着文件将更新):

图 46

图 46 Test Merge 的结果

在她单击 OK 后,John 在分支中所做的更改将会合并到她的工作副本中,她可以正常地提交更改。按照惯例,Josephine 应该在提交注释中填写一些描述性文本,以便团队成员可以了解哪些更改已合并到主干中。当 Oracle JDeveloper 支持 Subversion 1.5 时,此跟踪将会自动进行:

图 47

图 47 提供注释指定合并的更改

现在,在 John 完成分支的稳定且分支已可用于生产后,他将按之前所述标记代码。他还将删除他的本地工作副本(指向 stabilization_1.1 分支),并签出一个新的主干副本以便他能在该项目上继续工作。一个简便的方法是,将他的工作副本改为指向分支。为此,他需要选择 Versioning 菜单中的 Switch… 并提供主干的位置:

图 48

图 48 将工作副本重新指向主干

Oracle JDeveloper 会将 Josephine 在主干中所做的更改更新到 John 的工作副本的所有文件中,并将工作副本中的指针切换到指向主干。最后,因为已经不再需要 stabilization_1.1 分支,所以 John 将其从信息库中删除。为此,他需要显示 Versioning Navigator 窗口:

图 49

图 49 显示 Versioning Navigator

然后他找到了分支,右键单击该分支并选择 Delete

图 50

图 50 删除分支

如往常一样,John 在对信息库执行此操作时提供了注释:

图 51

图 51 删除分支的注释

总结

您已经了解了如何结合使用 Oracle JDeveloper 11g 和 Subversion 信息库在具有多个开发人员和多个开发分支的团队环境中工作。在本系列的第三篇文章中,您将看到开发人员使用 Oracle 应用开发框架时可能出现的一些“问题”。

转到第 3 部分 | 返回目录

 

更多精彩内容


John Stegeman (http://stegemanoracle.wordpress.com) 是 Cambridge Solutions(一家跨国 BPO 和 IT 服务公司)的 EMEA 地区的 Oracle ACE 总监(Oracle 融合中间件)兼首席架构师。他从 1990 年开始就使用 Oracle 产品工作,从版本 3 开始就使用 Oracle JDeveloper 了。