
迁移至 MQL5 Algo Forge(第 2 部分):使用多个存储库
概述
在第一篇文章中,我们开始从 MetaEditor 中内置的基于 SVN 的 MQL5 存储过渡到基于 Git 版本控制系统的更灵活、更现代的解决方案:MQL5 Algo Forge 。采取这一步骤的主要原因是,在处理多个项目或单个项目中的不同功能时,需要充分利用存储库分支。
转换始于在 MQL5 Algo Forge 中创建一个新的存储库,并使用 Visual Studio Code 设置本地开发环境,以及必要的 MQL5 和 Git 扩展和支持工具。然后,我们将 .gitignore 文件添加到存储库中,以从版本控制中排除标准和临时文件。所有现有项目都上传到专用 archive 分支,作为所有以前编写的代码的档案存储。main 分支留空,准备组织新的项目分支。通过这种方式,我们为在存储库的不同分支中分发不同的项目代码奠定了基础。
然而,自第一篇文章发表以来,MetaEditor 已显著扩展了对新存储库系统的支持。这些变化促使我们重新考虑之前概述的方法。因此,在本文中,我们将稍微偏离最初的计划,探索如何创建一个将其他公共项目作为组件集成的公共项目。我们将重点关注的项目是开发多币种 EA 交易系统。已经发表了几篇文章,描述了此项目的代码开发和修改方法。现在,我们将充分利用 Git 版本控制来组织和简化开发过程。
规划路径
很难相信,但在上一篇文章发表时,MetaEditor 尚未包含用于处理 MQL5 Algo Forge 存储库的 Git:菜单或文件上下文菜单命令。因此,使用 Visual Studio Code 等外部工具配置工作流需要付出相当大的努力。由于我们不知道 MetaEditor 最终将如何实现存储库支持,我们不得不使用当时可用的工具。
从那时起,新的 MetaEditor 版本引入了对 MQL5 Algo Forge 的内置支持。MetaQuotes 还发布了一篇新文章 “MQL5 Algo Forge 入门”,其中讲解了基础知识并演示了主要功能。然而,我们认为最重要的进展是在 MetaEditor 中实现共享项目。
这为什么很重要?之前,我们知道 MQL5 文件夹将作为托管在 MQL5 Algo Forge 的 Git 服务器上的存储库。显然,这个存储库将具有固定名称 mql5 。这意味着每个用户在 Algo Forge 中都会有一个名为 mql5 的存储库。安装新终端、登录社区并连接存储库后,该存储库将被克隆到 MQL5 文件夹中。与此同时,MQL5 Algo Forge 始终允许创建其他存储库。更准确地说,不是额外的,而是单独的,与 mql5 存储库无关。当然,这就提出了一个问题:MetaEditor 将如何处理这些其他存储库?
用户是否能够选择在终端的每次安装中使用哪个存储库?或者不是这样?是否只支持 mql5 存储库,用户被迫将他们的工作分成不同项目的分支?我们最初为这种最坏的情况做了准备。在单个存储库中跨分支管理多个项目并不特别方便。幸运的是,我们的担忧被证明是没有根据的。
MetaQuotes 推出了一种更为优雅的解决方案,可以同时有效地解决两个问题。一方面,我们有名为 mql5 的主存储库。对于那些已经习惯 MQL5 Storage 的人来说,这非常有效。他们现在可以继续使用版本控制,而不用担心下面是哪个版本控制系统。
另一方面,所有其他用户存储库现在都可以作为共享项目中的文件夹使用。这提供了一个新的标准根文件夹,与现有的(例如 MQL5 、 MQL5/Experts 、 MQL5/Include 等)一起,用于存储来自用户附加存储库的代码。
让我们来看一个例子。假设我们在 MQL5 Algo Forge 中有两个单独的存储库,这两个存储库都不是默认的 MQL5。第一个存储库( Adwizard )仅包含库代码,即仅 *.mqh 包含文件,而没有任何可以编译成 EA 交易或指标的 *.mq5 文件。第二个存储库( Simple Candles )包含使用第一个存储库中的包含文件的 *.mq5 文件。为简单起见,我们将第一个存储库称为库存储库,第二个称为项目存储库。
我们的目标是确定如何在项目存储库中开发时使用库存储库中的代码。例如,如果 mql5.com 代码库中共享的代码也被 MQL5 Algo Forge 中的作者镜像为公共存储库,这种情况可能会变得越来越普遍。在这种情况下,将一个或多个存储库作为外部库链接到项目的处理方式与我们将在本文中探讨的方式相同。
开始使用
让我们首先从拥有这两个存储库的开发人员的角度来看情况。这意味着我们可以自由地更改任何存储库中的代码,而无需通过拉取请求机制等待审查和批准。我们首先创建一个干净的终端文件夹,并从任何先前安装的 MetaTrader 5 副本中复制两个文件:
为了避免在文件系统深处搜索终端的工作文件夹,我们建议在便携模式下运行终端。在 Windows 中,一种方法是创建终端可执行文件的快捷方式,并将 /portable 标志添加到快捷方式属性中的目标字段。
启动终端后,为了以防万一,请开设一个新的模拟账户,更新到最新版本,登录社区,然后按 F4 启动 MetaEditor。如果 MQL5 Algo Forge 尚未自动连接,请连接它。
在导航器中,我们现在看到 “Shared Projects”(共享项目)文件夹,其中列出了我们之前通过 Web 界面创建的存储库。但是,如果我们在资源管理器中打开此文件夹,它仍然是空的。这意味着实际的存储库文件尚未下载到我们的计算机上。
要克隆它们,请右键单击每个所需的存储库,然后从上下文菜单中选择“Git 克隆”。日志确认 Adwizard 和 Simple Candles 均已成功克隆。带有克隆存储库的文件夹现在出现在资源管理器中:
此时,这两个项目的代码都可以在本地使用。
第一个问题
让我们打开 SimpleCandles.mq5 并尝试编译它:
正如预期的那样,出现编译错误。让我们尝试去理解错误的原因。这些错误并不严重,因为我们知道代码之前已经成功编译。唯一改变的是库和项目文件的相对位置。前两个基本错误源于编译器无法在预期的位置找到库文件。回到第 28 部分,我们同意使用以下文件夹结构来存储库和项目部分:
也就是说,我们将库存储库存储在项目存储库的子文件夹中。这为我们提供了一个可预测的结构来定位库文件。但这次,我们将改变这一点。我们将使用 MQL5/Shared Projects 文件夹,而不是使用项目内部强制的 Include 子文件夹。理想情况下,此文件夹将保持稳定,并在未来的 MetaEditor 版本中继续发挥相同的作用。
为了解决这个问题,我们将更新两个文件中的 #include 指令。但在进行任何代码更改之前,让我们遵循良好的开发实践,为这个孤立的任务创建一个单独的分支。一旦测试了修复,我们就可以将分支合并回主开发分支。
让我们看看项目存储库中已经有哪些分支。可以用多个方法进行:
- 通过 MetaEditor 中的存储库文件夹的上下文菜单:
- 通过存储库分支页面的 Web 界面:
- 使用外部 Git 工具,例如 Visual Studio Code。在存储库名称旁边,我们看到 main ,它是当前分支的名称。点击它会显示可用分支的列表(以及创建新分支的菜单项):
目前,该存储库有四个分支:
- main —— 主分支。它是使用存储库创建的。在最简单的情况下,所有工作都可以在这里完成,而无需创建任何额外的分支。在更复杂的情况下,此分支用于存储提供代码稳定版本的文件的状态。任何尚未完成和测试的正在进行的更改都会在其他分支中进行。
- develop —— 开发分支。在简单的情况下,它可以用作添加更改和实现新功能的唯一分支。如果新功能是按顺序实现的,则此选项就足够了。也就是说,在添加了前面的功能后,项目处于完全功能和稳定的状态之前,我们不会开始实现新功能。在开始开发新功能之前,分支会被合并:在 develop 分支中所做的编辑会合并到 main 中。如果同时开发多个功能,在一个开发分支中工作会变得不方便。在这种情况下,可以创建其他特征分支。
- 此类分支的示例为 article-17608-close-manager 和 article-17607 。前者是基于盈亏阈值的平仓逻辑的功能分支。该分支已经合并到 develop 中,然后 develop 又合并到 main 中。第二个功能分支用于自动优化的增强。它仍在进行中,尚未合并到 develop 或 main 中。
需要强调的是,Git 并不强制执行任何特定的分支使用规则。因此,我们可以选择对我们来说最方便的选项。一些开发人员发现某些工作流程很有用,并与他人分享。这就是“最佳实践”的出现方式。您可以自由采用或调整任何适合您项目的分支模型。作为示例,请查看此文章中描述的其中一个建议的分支原则。
现在让我们回到我们的存储库。
可能引起疑问的一个细节是前缀 origin/ (或 MetaEditor 中显示的 refs/remotes/origin/ )。此前缀仅表示分支存在于远程存储库中,而不仅仅是本地。通常,我们保持本地和远程分支同步。在 MetaEditor 中,运行提交(Commit)命令会自动触发推送(Push)命令,从而将提交发送到远程存储库。
如果在 MetaEditor 之外进行提交,则可以在本地提交而无需推送。在这种情况下,同名的本地和远程分支可能不同。origin/ 前缀有助于区分它们。使用此前缀,我们表示远程存储库中的分支;否则,这是本地的。
创建新分支
由于计划的编辑仅确保代码在其库部分的位置发生变化后能够正确编译,因此我们将基于从 develop 生成的新分支。我们首先切换到 origin/develop ,之后它作为本地 develop 分支出现在列表中。
然后我们创建一个新的分支(执行新建命令)并输入所需的名称。按照我们的惯例,与文章相关的分支以 article- 加上文章的唯一标识符开头。这之后可以选择一个简短的后缀来描述文章的主题。这就是为什么新分支被命名为 “article-17698-forge2”。
我们也可以通过其他方式创建分支:使用 Web 界面、Visual Studio Code 界面或命令行界面。从存储库根文件夹中的命令行,我们可以运行:
git checkout -b article-17698-forge2 develop
这告诉 Git 切换(checkout)到基于 develop 分支、名为 article-17698-forge2 的新分支 (-b)。
如果在 web 界面之外创建分支,则它将仅存在于我们的本地计算机上,直到第一次推送到远程存储库。反之亦然:如果通过 web 界面在远程存储库中创建分支,则在首次从远程存储库拉取之前,它不会出现在我们的本地计算机上。
您可以像这样推送更改:
或者像这样:
当 Push 操作包含创建新分支时,其控制台命令必须包含附加参数,以确认我们确实希望在远程存储库中创建该分支:
git push --set-upstream origin article-17698-forge2
此后,该分支既存在于存储库的本地副本中,也存在于 MQL5 Algo Forge 中托管的远程存储库中。此时,我们可以开始进行编辑,而不必担心破坏其他分支的功能。
做出修改
所需的修改非常简单。在文件 SimpleCandles.mq5 中,我们更新包含来自 Adwizard 库的文件的行。由于 Simple Candles 和 Adwizard 存储库的根文件夹现在位于 Shared Projects 文件夹内的同一级别,因此 Expert.mqh 的路径必须先向上移动一级 (../),然后再下降到库存储库的子文件夹:
#include "Include/Adwizard/Experts/Expert.mqh" #include "../Adwizard/Experts/Expert.mqh"
文件 Strategies/SimpleCandlesStrategy.mqh 中也需要进行类似的调整:
#include "../Include/Adwizard/Virtual/VirtualStrategy.mqh" #include "../../Adwizard/Virtual/VirtualStrategy.mqh"
经过这些更改后, SimpleCandles.mq5 可以再次成功编译。我们现在可以将更改提交到存储库:
如前所述,通过 MetaEditor 界面提交时,会自动执行 Push 命令,将更改发送到 MQL5 Algo Forge 中的远程存储库。
使用控制台命令时,我们可以实现以下相同的结果。如果除了编辑现有文件外,我们还创建了新文件,我们首先需要将它们添加到存储库索引中:
git add。
此命令添加存储库文件夹中找到的所有新文件。若要仅添加特定文件,请将点(.)替换为其名称。之后,我们使用指定的注释提交更改,并将其推送到远程存储库:
git commit -m "Fix relative paths to include files from Adwizard"
git push
至此, article-17698-forge2 分支的变更已经完成。它可以合并到 develop 分支然后关闭。
第二个问题
在这里,我们遇到了一个令人不快的惊喜。MetaEditor 目前缺少合并分支的工具。换句话说,我们现在可以创建新的分支,但我们不能将更改从一个分支转移到另一个分支!希望在不久的将来能添加此功能。目前,我们必须再次转向其他工具来执行存储库操作。
我们可以通过两种主要方式合并分支。第一种是使用 Visual Studio Code 中的合并界面或标准控制台命令。对于我们的情况,可以使用以下命令:
git checkout develop
git pull
git merge --no-ff article-17698-forge2
首先,我们切换到 develop 分支。然后,作为预防措施,我们更新它(以防所做的更改尚未到达我们的本地计算机)。最后,最后一个命令执行实际的合并。合并冲突是可能的,但在我们的场景中,它们不太可能发生,因为我们仍在考虑单个开发人员在项目上工作的情况。即使在多个位置工作,只要我们定期将存储库更新到最新状态,就不会出现冲突。
然而,我们不要纠缠于这种方法的细微差别。相反,我们将仔细研究第二种方法。这里我们使用 MQL5 Algo Forge web 界面。
使用拉取请求进行合并
与其他基于 Git 的平台(例如 GitHub、GitLab 或 Bitbucket)一样,MQL5 Algo Forge 也包含一种称为拉取请求 (Pull Request,PR) 的机制。
拉取请求允许开发人员建议将分支中的更改合并到存储库中。换句话说,创建 PR 是通知存储库所有者和其他贡献者的一种方式:“我已经完成了我分支上的工作,请审核并将这些更改合并到主分支(main/master/develop)中。”
PR 不是 Git 本身的功能,而是在其之上构建的附加层。它在将更改合并到主分支之前组织代码审查和讨论过程。
拉取请求还解决了现代开发中的其他几个关键任务:与自动化测试的持续集成 (CI)、其他开发人员的质量控制以及以 PR 评论的形式记录更改,解释为什么进行某些修改。然而,这些实践与基于团队的项目最为相关,而 MQL5 项目通常是单独的项目。无论如何,随着未来协作项目的出现,工作流程可能会变得更加重要。
也就是说,我们已经复制了使用 PR 添加新功能或修复的典型工作流程的开始:
-
拉取最新的更改。在开始工作之前,我们更新了本地 develop 分支。
-
为该任务创建一个新的分支。从更新的 develop 分支中,我们创建了一个具有明确名称的分支 article-17698-forge2 。
-
在新分支中进行更改。我们修改并测试了几个文件,然后提交了更改。
-
创建拉取请求。在 MQL5 Algo Forge 网页界面中,导航到“拉取请求”选项卡并单击红色的大“创建拉取请求”按钮。
这将打开分支选择页面。在这个阶段,PR 尚未创建;首先,我们必须定义将在哪里合并更改。一旦选择了分支,我们就可以查看更改列表。然后,再次单击“创建拉取请求”。
将打开一个新页面,我们可以在其中提供更改的详细描述。在这里,我们还可以指定审阅者。默认情况下,请求是针对我们自己的,这正是我们在这种情况下所需要的。
-
回顾与讨论。由于我们是单独工作的,我们可以跳过这一步。通常,此阶段包括:
-
审阅者检查代码并留下注释(一般或与特定行相关),
-
PR 作者在同一分支机构回复评论并进行更正,
-
所有新推送都会自动添加到现有的 PR 中。
-
-
合并。审阅者批准(如果有)并且 CI 通过(如果已配置)后,PR 可以合并。通常,有几种合并选项:
-
合并提交: 创建单独的合并提交,保留分支历史记录。
-
压缩并合并: 将所有 PR 提交合并到添加到目标分支的单个提交中。有助于避免因“修复拼写错误”等小提交而使历史记录混乱。
-
重新定基并合并: 在目标分支(在我们的例子中是 develop)上重新应用 PR 提交。这会产生清晰、线性的历史记录。
现在到了拉取请求操作的最后一页。这里我们勾选‘删除分支...’选项来关闭临时开发分支。提交历史记录仍将反映分支的存在。但既然我们已经实现了目标,保持开放就没有任何意义。对于解决其他任务的未来更改,我们将创建新的分支。这样,存储库的分支列表始终提供当前正在进行的并行工作的清晰快照。
保留其余设置并单击“创建合并提交”。
该过程现已完成: article-17698-forge2 分支已合并到 develop 分支并被删除:
一般来说,即使在您自己的存储库中使用拉取请求也是一种很好的推荐做法,即使是对于单个项目也是如此。在合并之前,您可以直观地查看所有更改,通常会发现在提交过程中遗漏的东西:不必要的注释、零散的文件或非最佳的编辑。本质上,这是一种自律。此外,采用这种工作流程可以养成良好的习惯(分支、代码审查等)。因此,如果你以后加入一个开发团队,这个过程对你来说已经很熟悉了。
所以,是的,向自己提交 PR 不仅是可能的,而且对于任何严肃的项目来说都是鼓励的。它提高了代码质量并加强了纪律。当然,对于由一两次提交组成的快速修复,没有什么可以阻止您直接使用 git merge 进行合并。但对于较大的变化,PR 是更好的方法。
结论
总体而言,个人存储库的工作流程现在已经建立得很好。我们已经介绍了从克隆存储库到改进过程的路径,在这个过程中,更改使代码功能正常,并为进一步开发做好了准备。项目存储库现在可以有意义地使用来自另一个(或多个)存储库的代码作为库。
然而,我们只考虑了一种情况:当同一用户同时拥有项目和库存储库时。另一种情况是,项目所有者希望将其他人的存储库用作库。这种情况并没有那么简单。然而,这种工作流程,积极重用社区代码和协作,是迁移到新存储库系统背后的既定目标之一。然而,基础已经奠定。
我们暂时停在这里。下篇再见!
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17698
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。




西里尔字母几乎不存在--我甚至很久以前就放弃在评论中使用它了。
显然,我错了。我刚刚在 .mq5 文件中添加了UTF-8 编码:
// 西里尔文
保存后,文件编码变为 "UTF-16 LE BOM"。
看来是 MetaEditor 的错。我添加了西里尔字符,然后用 Notepad++ 保存文件,编码仍然是 UTF-8。
我还觉得奇怪的是,为什么要从本地仓库删除分支呢?鉴于 Git 的分支模型是它的杀手锏,而且 Git 鼓励频繁创建、删除和合并分支。
所以我也赞成在分支与主分支合并后删除它们。只是我第一次听说,在删除之后,他们会为新的剪辑创建一个同名的分支,而不是一个新的分支。
观察差异的目的是什么?
是的,这是非常必要的。我也在积极使用它,但仅限于 VS Code。奇怪的是,尽管我查看了编码 "糟糕 "的文件,但并没有出现崩溃现象。
我从没遇到过这种情况。这也太出乎意料了。也许是因为同时使用不同的 ME 版本来处理相同的文件而导致正常文件损坏?我不知道...
我查看了提交历史,发现两个月前添加的文件确实已经使用了UTF-8编码,而三个月前添加的文件仍然是UTF-16 LE。很显然,在那段时间里,有一次改用了 UTF-8 编码。
我想我错了。我刚刚在 .mq5 文件中添加了UTF-8 编码:
并在保存后将文件编码更改为 "UTF-16 LE BOM"。
看来是 MetaEditor 的错。我添加了西里尔字符,然后用 Notepad++ 保存文件,编码仍然是 UTF-8。
我确认,添加俄文字母并保存文件后,编码从 UTF-8 变为 UTF-16 LE。如果删除所有俄文字母并保存,编码仍为 UTF-16 LE。
这似乎是 MetaEditor 的错。
这里有一个证明,可以让 UTF-8、西里尔文和 Git 兼容:
https://forge.mql5.io/junk/utf8-cyrillic-test/commit/e87d37b02e88d44305dea0f7f6630c6173471aea
只需要求 MetaEditor 不要更改文件编码即可。
我想我错了。我刚刚在 .mq5 文件中添加了UTF-8 编码:
并在保存后将文件编码更改为 "UTF-16 LE BOM"。
看来是 MetaEditor 的错。我添加了西里尔字符,然后用 Notepad++ 保存文件,编码仍然是 UTF-8。
很可能是UTF-8没有BOM,ME不喜欢它。至少以前只有在 BOM 存在的情况下,ME 才会让文件保持 UTF-8 编码。其他编辑器更聪明,可以在没有 BOM 的情况下工作。