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