3 个稳定版本
| 1.1.0 | 2022年9月10日 |
|---|---|
| 1.0.1 | 2022年9月5日 |
| 1.0.0 | 2022年9月4日 |
在 开发工具 中排名 #2074
120KB
2.5K SLoC
monorail
将任何 git 仓库转换为单轨仓库。
monorail 将任何 git 仓库转换为基于主干的开发单轨仓库。它使用一个描述各种目录路径及其之间关系的文件,从上次标签以来提取 git 的更改,并推导出已更改的内容。然后可以将这些更改提供给其他程序(例如 monorail-bash),这些程序可以对这些更改采取行动。虽然 monorail 目前仅支持 git 作为 VCS 后端,但可以添加对其他后端的支持。
monorail 简而言之
- 一个
Monorail.toml文件,描述您的仓库布局 - 命令
monorail inspect change,它读取Monorail.toml,分析 refs 之间的 git 状态(通常是在最近的 git 注释标签和 HEAD 之间),并返回已更改的内容 - 程序
monorail-bash,它直接执行用户定义的bash脚本,或者根据monorail inspect change的输出执行 - 命令
monorail release,它创建注释标签(即monorail变更检测的“检查点”)
下面是一个教程,介绍了如何使用 monorail 和 monorail-bash。
安装
请确保以下内容已安装并可在系统路径上使用
可以通过克隆仓库并从仓库根目录执行以下命令从源安装 monorail 和所有扩展
./install.sh
默认情况下,它将把这些放在/usr/local/bin,如果希望放在其他位置,请使用以下命令:./install.sh <destination>。
请注意,虽然monorail可以通过cargo install从crates.io安装,但cargo不支持安装额外的资源,例如monorail-bash的脚本入口点。install.sh脚本处理二进制和扩展安装。
命令
使用monorail help
配置
在你想使用monorail和扩展的存储库根目录中创建一个Monorail.toml配置文件,参考Monorail.reference.toml文件以获取注释示例。
词汇表
- 项目:一个作为单个单元开发/部署/测试的路径
- 组:包含一组项目和相关配置的路径
- 依赖:项目可以声明为依赖项的路径,该路径上的任何更改都将视为对依赖于该路径的项目进行更改
- 链接:可以声明为组中所有项目的自动依赖项的路径,该路径上的任何更改都将视为所有项目的更改
- 目标:一个可以检测更改的路径的总称,可以对其运行命令。组、项目、依赖或链接之一。
- 扩展:运行用户定义的以受支持语言编写的代码;例如
bash - 命令:由目标定义的函数,可以通过执行器依赖的方式调用
教程
注意:本教程假定您在一个类UNIX环境中。
在本教程中,您将学习
- 如何声明组、项目、依赖和链接
- 如何检查更改
- 如何定义命令
- 如何执行命令
- 如何发布
首先,创建一个新的git仓库,并创建另一个作为远程仓库
注意:这假设有一个init.defaultBranch的master或空字符串,这是git的默认值。如果您的不同,请将git命令中的git push命令中的master更改为该值。
git init monorail-tutorial
git init monorail-tutorial-remote
REMOTE_TRUNK=$(git -C monorail-tutorial-remote branch --show-current)
git -C monorail-tutorial-remote checkout -b x
pushd monorail-tutorial
git remote add origin ../monorail-tutorial-remote
git commit --allow-empty -m "HEAD"
git push --set-upstream origin $REMOTE_TRUNK
popd
注意:提交是为了创建有效的HEAD引用,而在远程分支上的分支检出是为了避免在教程中推送时从git中产生的receive.denyCurrentBranch错误。
要开始,请使用以下shell命令生成以下目录结构
cd monorail-tutorial
mkdir -p group1/project1
touch Monorail.toml
... 生成以下目录结构
├── Monorail.toml
└── group1
└── project1
注意:本教程的其余部分将使用heredoc字符串更新Monorail.toml文件,以方便起见。
执行以下命令,在Monorail.toml中指定新组、项目和随后在教程中使用的extension
cat <<EOF > Monorail.toml
[vcs]
use = "git"
[vcs.git]
trunk = "$(git branch --show-current)"
[extension]
use = "bash"
[[group]]
path = "group1"
[[group.project]]
path = "project1"
EOF
总结
您的monorail配置文件(默认:Monorail.toml)以monorail概念术语描述您现有的存储库布局。一个project是一个作为单元开发/部署/测试的路径,例如后端服务、Web应用程序等。一个group是一组相关项目,并定义了项目之间可以共享的内容(关于共享的更多内容将在后面介绍)。
最后,许多单轨系统的功能是基于路径的。我们对group.path(相对于仓库根目录)和project.path(相对于指定的group.path)的定义声明了这些对象在我们仓库中的位置。
检查更改
monorail会检测自上次发布以来的更改;参见:发布。对于git,这意味着自上次由monorail release创建的标注标签以来的未提交、已提交和已推送的文件。
显示无更改的检查
首先查看inspect change的输出
monorail inspect change | jq .
{
"group": {
"group1": {
"change": {
"file": [],
"project": [],
"link": [],
"depend": []
}
}
}
}
如预期,没有更改,并且monorail能够成功查询git并使用Monorail.toml配置。随着教程的进行,将解释file、project、link和depend字段的含义。
显示更改的检查
为了展示在monorail输出中实际更改的样子,在project1中创建一个新文件
touch group1/project1/foo.txt
再次查看更改
monorail inspect change | jq .
{
"group": {
"group1": {
"change": {
"file": [
{
"name": "group1/project1/foo.txt",
"project": "group1/project1",
"action": "use",
"reason": "project_match"
}
],
"project": [
"group1/project1"
],
"link": [],
"depend": []
}
}
}
}
monorail已确定新添加的文件代表了一次有意义的更改,这是基于我们在Monorail.toml中的配置。
change.file数组包含一个包含检测到的更改元数据的对象的列表。它包含文件的name(相对于仓库根目录的路径),文件所属的project》,monorail在更改检测过程中采取的action(例如,它被use了),以及采取action的原因(例如,它与声明的项目匹配)。
change.project数组包含一个包含检测到更改的项目相对于仓库根目录的路径列表。该列表在所有change.file条目中去重;项目最多在此列表中显示一次。
在提交或推送后显示更改的检查
这种对已更改内容的理解在提交和推送之间持续存在。提交您的更改
git add * && git commit -am "x"
然后,再次查看更改
monorail inspect change | jq .
{
"group": {
"group1": {
"change": {
"file": [
{
"name": "group1/project1/foo.txt",
"project": "group1/project1",
"action": "use",
"reason": "project_match"
}
],
"project": [
"group1/project1"
],
"link": [],
"depend": []
}
}
}
}
monorail仍然知道对项目group1/project1的更改。
推送,并再次查看更改
git push && monorail inspect change | jq .
输出保持不变。
声明依赖项和链接
monorail允许项目依赖于声明路径之外的路径。这允许引用路径包含实用代码、序列化文件(例如protobuf定义)、配置等。当这些路径有更改时,它将触发依赖于它们的项目的更改。
依赖关系
首先创建一个用作依赖项的目录,以及一个用于存放新项目project2的目录
mkdir -p group1/common/library1
mkdir group1/project2
执行以下命令以调整Monorail.toml中的[[group]]部分,将library1添加为可依赖的路径,指定project2,并使project2依赖于library1
cat <<EOF > Monorail.toml
[vcs]
use = "git"
[vcs.git]
trunk = "$(git branch --show-current)"
[extension]
use = "bash"
[[group]]
path = "group1"
depend = [
"common/library1"
]
[[group.project]]
path = "project1"
[[group.project]]
path = "project2"
depend = [
"common/library1"
]
EOF
在 group 部分中的 depend 声明表示此路径 可以被 依赖。在 project.depend 中指定项目所依赖的零个或多个此类路径。
要触发变更检测,在 library1 中创建一个文件
touch group1/common/library1/foo.proto
然后 monorail inspect change | jq .
{
"group": {
"group1": {
"change": {
"file": [
{
"name": "group1/common/library1/foo.proto",
"project": null,
"action": "use",
"reason": "project_depend_effect"
},
{
"name": "group1/project1/foo.txt",
"project": "group1/project1",
"action": "use",
"reason": "project_match"
}
],
"project": [
"group1/project2",
"group1/project1"
],
"link": [],
"depend": [
"group1/common/library1"
]
}
}
}
}
我们的原始文件条目仍然存在,但出现了新创建文件的另一个条目。它有一个 project 值为 null,因为它不在项目的路径中,并且有一个 reason 表示它被使用是因为项目依赖于包含此文件的路径(project_depend_effect)。
在 group.project 中出现了一个 group1/project2 条目,表示该项目现在是已更改项目集合的一部分。我们没有在 project2 中更改任何文件(实际上,根本不存在!)但修改了 project2 依赖的路径。
此外,在我们的库路径中,在 group.depend 中出现了一个条目。
链接
链接与 depend 的工作方式类似,但它应用于组中的所有项目,而无需它们进行选择。为了演示,我们将创建第三个项目和虚构的 Lockfile 以将所有项目链接到
mkdir group1/project3
touch group1/Lockfile
执行以下操作以调整 [[group]] 部分 Monorail.toml 以指定此新项目,以及一个组 link
cat <<EOF > Monorail.toml
[vcs]
use = "git"
[vcs.git]
trunk = "$(git branch --show-current)"
[extension]
use = "bash"
[[group]]
path = "group1"
depend = [
"common/library1"
]
link = [
"Lockfile"
]
[[group.project]]
path = "project1"
[[group.project]]
path = "project2"
depend = [
"common/library1"
]
[[group.project]]
path = "project3"
EOF
执行 monocle inspect change | jq . 得到
{
"group": {
"group1": {
"change": {
"file": [
{
"name": "group1/Lockfile",
"project": null,
"action": "use",
"reason": "group_link_effect"
},
{
"name": "group1/common/library1/foo.proto",
"project": null,
"action": "use",
"reason": "project_depend_effect"
},
{
"name": "group1/project1/foo.txt",
"project": "group1/project1",
"action": "use",
"reason": "project_match"
}
],
"project": [
"group1/project3",
"group1/project2",
"group1/project1"
],
"link": [
"group1/Lockfile"
],
"depend": [
"group1/common/library1"
]
}
}
}
}
同样,我们对 project1 和 library1 的原始更改仍然存在。出现了新的 change.file 条目 Lockfile,为 project3 添加了新的 change.project,并且 change.link 现在具有我们更改的 Lockfile 的路径。
请注意,project3 不需要明确依赖于 Lockfile;只需是 group1 的成员即可。
定义命令
命令通过扩展运行,扩展是用户定义代码的“运行器”。我们已经在 Monorail.toml 中指定了以下内容,因此我们将继续使用 monorail-bash
[extension]
use = "bash"
命令存储在针对每个目标的文件中,其路径在 Monorail.toml 中定义。在我们的情况下,该路径将是相对于 group1/project1 的 support/script/monorail-exec.sh(默认值)。
使用以下命令创建此路径
mkdir -p group1/project1/support/script
在 group1/project1/support/script/monorail-exec.sh 文件中,我们将定义一个包含三个命令的脚本
cat <<"EOF" > group1/project1/support/script/monorail-exec.sh
#!/usr/bin/env bash
function command1() {
echo "Hello, from command1"
echo "The calling environment is inherited: ${SOME_EXTRA_VAR}"
echo "some data" > side_effects.txt
}
function command2() {
echo "Hello, from command2"
cat side_effects.txt
}
function setup() {
echo "Installing everything you need"
}
EOF
命令名称可以是任何有效的 UTF-8 字符串,并且可以执行任何正常 bash 脚本可以执行的操作:源其他脚本、调用外部构建工具、执行网络请求等。使用 monorail 的好处之一是它不会限制您可以使用的构建工具。
执行命令
定义了命令脚本后,可以使用 monorail-bash exec 调用它。这可以通过两种方式之一完成
- 隐式地,从
monorail的变化检测输出中 - 显式地,通过指定目标列表
隐式
当隐式执行时,monorail-bash exec使用与monorail inspect change相同的进程来推导变化目标并对它们执行命令。为了说明这一点,请使用以下内容(SOME_EXTRA_VAR仅表示父shell值可以被传递给命令)
SOME_EXTRA_VAR=foo monorail-bash -v exec -c command1 -c command2
Sep 10 07:34:07 monorail-bash : 'monorail' path: monorail
Sep 10 07:34:07 monorail-bash : 'jq' path: jq
Sep 10 07:34:07 monorail-bash : 'git' path: git
Sep 10 07:34:07 monorail-bash : use libgit2 status: false
Sep 10 07:34:07 monorail-bash : 'monorail' config: Monorail.toml
Sep 10 07:34:07 monorail-bash : working directory: /Users/patrick/lab/github.com/pnordahl/monorail-tutorial
Sep 10 07:34:07 monorail-bash : command: command1
Sep 10 07:34:07 monorail-bash : command: command2
Sep 10 07:34:07 monorail-bash : start:
Sep 10 07:34:07 monorail-bash : end:
Sep 10 07:34:07 monorail-bash : target (inferred): group1/Lockfile
Sep 10 07:34:07 monorail-bash : target (inferred): group1/common/library1
Sep 10 07:34:07 monorail-bash : target (inferred): group1/project2
Sep 10 07:34:07 monorail-bash : target (inferred): group1/project3
Sep 10 07:34:07 monorail-bash : target (inferred): group1/project1
Sep 10 07:34:07 monorail-bash : NOTE: Ignoring command for non-directory target; command: command1, target: group1/Lockfile
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command1, target: group1/common/library1
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command1, target: group1/project2
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command1, target: group1/project3
Sep 10 07:34:07 monorail-bash : Executing command; command: command1, target: group1/project1
Hello, from command1
The calling environment is inherited: foo
Sep 10 07:34:07 monorail-bash : NOTE: Ignoring command for non-directory target; command: command2, target: group1/Lockfile
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command2, target: group1/common/library1
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command2, target: group1/project2
Sep 10 07:34:07 monorail-bash : NOTE: Script not found; command: command2, target: group1/project3
Sep 10 07:34:07 monorail-bash : Executing command; command: command2, target: group1/project1
Hello, from command2
some data
输出的大部分是工作流程和调试信息,但有几个关键点值得关注。
- 命令按照由
-c选项指定的顺序执行。 - 我们指定的作为
depend和link条目的路径可以指定它们自己的command1和command2命令的实现 - 指向单个文件的路径不能指定命令实现(例如,我们的Lockfile)
- 没有为
command1和/或command2指定实现的路径被记录并忽略
对monorail检测到的变化执行任意bash函数有许多应用,包括
- 对您修改的所有项目/依赖项/链接执行命令,而无需特别针对它们;
monorail-bash确保对每个变化目标,请求的命令按顺序执行 - 在CI中运行针对所有变化目标的特定命令(例如,
check、build、test、deploy等)
显式
手动选择目标使您能够独立于VCS变化检测执行命令。应用包括
- 帮助新开发者熟悉代码库,因为可以在一组命令中定义所有设置代码并对其执行,例如一个
bootstrap命令 - 运行任何目标在整个仓库中的任何命令
为了说明手动选择目标,我们将运行之前定义但未执行的setup命令。执行以下操作(删除-v以减少视觉噪音)
monorail-bash exec -t group1/project1 -c setup
Installing everything you need
有关更多信息,请执行monorail-bash -h和monorail-bash exec -h。
发布
monorail使用后端VCS的本地机制,例如git中的标签作为“发布”标记。这为变化检测创建了一个“检查点”。如果没有发布标签,monorail将被迫搜索git历史记录到第一个提交。这将是不高效的,并且使变化检测变得无用,因为所有目标在足够长的时间线上都被视为已更改。
执行发布时,它适用于自上次发布以来更改的所有目标(或如果尚未存在发布,则为存储库的第一个提交)。
首先,让我们提交并推送我们的当前更改
git add * && git commit -am "update commands" && git push
假设我们已经提交了我们打算提交的内容,并且目标命令已运行并令人满意(例如,CI已通过我们分支的合并),我们可以使用以下命令进行补丁发布的dry-run
monorail release --dry-run -t patch | jq .
{
"id": "v0.0.1",
"targets": [
"group1/Lockfile",
"group1/common/library1",
"group1/project1",
"group1/project2",
"group1/project3"
],
"dry_run": true
}
monorail使用适合所选VCS约定的id创建发布;在这种情况下,这是git semver标签格式。它还将在targets数组中嵌入包含在本发布中的目标列表;对于git,它将在这个发布消息中嵌入目标列表。
现在,运行一个真正的发布
monorail release -t patch | jq .
{
"id": "v0.0.1",
"targets": [
"group1/Lockfile",
"group1/common/library1",
"group1/project1",
"group1/project2",
"group1/project3"
],
"dry_run": false
}
为了表明发布清除了monorail对变化的观点,请执行以下操作
monorail inspect change | jq .
{
"group": {
"group1": {
"change": {
"file": [],
"project": [],
"link": [],
"depend": []
}
}
}
}
最后,我们新推送的标签现在在远程。要查看此内容,请执行以下操作
git -C ../monorail-tutorial-remote show -s --format=%B v0.0.1
... 输出结果
tag v0.0.1
Tagger: you <email@domain.com>
group1/Lockfile
group1/common/library1
group1/project1
group1/project2
group1/project3
本教程到此结束。您已经了解了 monorail 的核心概念及其扩展的工作方式,现在您可以将其用于实际项目。尝试不同的仓库布局、命令、持续集成(CI)以及基于主干开发的工作流程,以适应您的团队。
有关 monorail 及其扩展的各种配置的更多信息,请参阅 Monorail.reference.toml。
不变性
为了正常运行,monorail 需要以下不变性得到满足:
- 组不能引用其他组中的路径
- 目标不能引用其他目标中的路径
- 只能共享指定在组中的
link或depend配置中的路径
如果违反了其中任何一项,则 monorail 命令分析仓库更改的行为是未定义的。
开发设置
这将构建项目并运行测试
cargo build
cargo test -- --nocapture
您可以使用 install.sh 构建一个 monorail 的发布二进制文件并将其与扩展一起复制到您的 PATH 中。
依赖关系
~14MB
~308K SLoC