Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/tasks/task-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ Change the default directory tasks are run from.
dir = "{{cwd}}"
```

### `task_config.includes`
### `task_config.includes` {#task-config-includes}

Set the toml files and file-task directories mise should search when looking for tasks.

Expand Down Expand Up @@ -722,39 +722,43 @@ If you want auto-completion/validation in included toml tasks files, you can use

#### Remote Git Includes <Badge type="warning" text="experimental" />

You can include directories of tasks from git repositories using the `git::` URL syntax:
You can include directories or individual task toml files from git repositories using the `git::` URL syntax:

::: code-group

```mise-toml [ssh]
[task_config]
includes = [
"git::ssh://git@github.com/myorg/shared-tasks.git//tasks?ref=v1.0.0"
"git::ssh://git@github.com/myorg/shared-tasks.git//tasks?ref=v1.0.0",
"git::ssh://git@github.com/myorg/shared-tasks.git//tasks/release.toml?ref=v1.0.0",
]
```

```mise-toml [https]
[task_config]
includes = [
"git::https://github.com/myorg/shared-tasks.git//tasks?ref=main"
"git::https://github.com/myorg/shared-tasks.git//tasks?ref=main",
"git::https://github.com/myorg/shared-tasks.git//tasks/release.toml?ref=main",
]
```

:::

URL format: `git::<protocol>://<url>//<path>?<ref>`
URL format: `git::<protocol>://<url>//<path>?ref=<ref>`

Required fields:

- `protocol`: The git protocol (ssh or https).
- `url`: The git repository URL.
- `path`: The path to the directory in the repository.
- `path`: The path to a directory or a `.toml` task file in the repository.

Optional fields:

- `ref`: The git reference (branch, tag, commit). Defaults to the repository's default branch.

The repository will be cloned and cached in `MISE_CACHE_DIR/remote-git-tasks-cache`. Tasks from the included directory will be loaded as if they were local file tasks. You can disable caching with `MISE_TASK_REMOTE_NO_CACHE=true` or the `--no-cache` flag.
When `path` points at a directory, mise loads both executable file tasks and any `.toml` task files inside that directory. When `path` points at a single `.toml` file, only that file is loaded.

Included `.toml` files use the [task toml file format](#task-config-includes) (the keys are task names — there is no `[tasks.…]` prefix). The repository will be cloned and cached in `MISE_CACHE_DIR/remote-git-tasks-cache`. Tasks from the include will be loaded as if they were local. You can disable caching with `MISE_TASK_REMOTE_NO_CACHE=true` or the `--no-cache` flag.

## Monorepo Support <Badge type="warning" text="experimental" />

Expand Down
21 changes: 21 additions & 0 deletions e2e/helpers/scripts/git_http_backend_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,27 @@ def create_test_repo(repo_path):
ripgrep_file.write_text('#!/usr/bin/env bash\necho "ripgrep task executed"\n')
ripgrep_file.chmod(0o755)

# A toml task file colocated with the executable scripts. Keys are task
# names; values are the run command (or a table). Used by tests covering
# remote toml task includes.
tasks_toml = xtasks_dir / 'tasks.toml'
tasks_toml.write_text(
'toml_task = "echo toml_task executed"\n'
'\n'
'[toml_table_task]\n'
'run = "echo toml_table_task executed"\n'
'description = "TOML task with table form"\n'
)

# A standalone toml file in a sibling directory to test the
# `git::URL//path/to/file.toml` form.
standalone_dir = Path(repo_path) / 'xtasks' / 'standalone'
standalone_dir.mkdir(parents=True)
standalone_toml = standalone_dir / 'standalone.toml'
standalone_toml.write_text(
'standalone_task = "echo standalone_task executed"\n'
)

# Commit files
subprocess.run(['git', 'add', '.'], cwd=repo_path, check=True)
subprocess.run(['git', 'commit', '-m', 'Add test files'], cwd=repo_path, check=True)
Expand Down
37 changes: 37 additions & 0 deletions e2e/tasks/test_task_remote_git_includes
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,41 @@ assert_contains "mise tasks" "ripgrep"
assert_succeed "mise run local_task"
assert_succeed "mise run ripgrep"

mise cache clear # Clear cache to force redownload

#################################################################################
# Test remote git directory includes that contain toml task files
#################################################################################

cat <<EOF >mise.toml
[task_config]
includes = [
"git::${LOCAL_GIT_URL}//xtasks/lint"
]
EOF

# Tasks defined inside tasks.toml in the remote directory should be loaded
# alongside the executable script tasks.
assert_contains "mise tasks" "ripgrep"
assert_contains "mise tasks" "toml_task"
assert_contains "mise tasks" "toml_table_task"
assert_contains "mise run toml_task" "toml_task executed"
assert_contains "mise run toml_table_task" "toml_table_task executed"

mise cache clear # Clear cache to force redownload

#################################################################################
# Test remote git includes that point directly at a toml file
#################################################################################

cat <<EOF >mise.toml
[task_config]
includes = [
"git::${LOCAL_GIT_URL}//xtasks/standalone/standalone.toml"
]
EOF

assert_contains "mise tasks" "standalone_task"
assert_contains "mise run standalone_task" "standalone_task executed"

echo "All tests passed!"
13 changes: 10 additions & 3 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2281,7 +2281,7 @@ async fn load_tasks_includes(
if root.is_file() && root.extension().map(|e| e == "toml").unwrap_or(false) {
load_task_file(config, root, config_root, task_config_dir).await
} else if root.is_dir() {
let files = WalkDir::new(root)
let all_files = WalkDir::new(root)
.follow_links(true)
.into_iter()
// skip hidden directories (if the root is hidden that's ok)
Expand All @@ -2290,7 +2290,6 @@ async fn load_tasks_includes(
.map_ok(|e| e.path().to_path_buf())
.try_collect::<_, Vec<PathBuf>, _>()?
.into_iter()
.filter(|p| file::is_executable(p))
.filter(|p| {
!Settings::get()
.task
Expand All @@ -2299,10 +2298,18 @@ async fn load_tasks_includes(
.any(|d| p.starts_with(d))
})
.collect::<Vec<_>>();
let is_toml = |p: &Path| p.extension().map(|e| e == "toml").unwrap_or(false);
let (toml_files, exec_files): (Vec<_>, Vec<_>) = all_files
.into_iter()
.filter(|p| is_toml(p) || file::is_executable(p))
.partition(|p| is_toml(p));
let mut tasks = vec![];
for path in toml_files {
tasks.extend(load_task_file(config, &path, config_root, task_config_dir).await?);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment on lines +2307 to +2309

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 config_root for directory-resident .toml tasks differs from neighbouring executable tasks

load_task_file sets task.config_root to the config_root parameter, which here is the parent config's root (the directory containing mise.toml). Executable tasks loaded from the same directory get root (the scanned directory itself) as their root via Task::from_path. Any relative path used in a toml task's run, dir, or similar fields will therefore resolve against the parent config root instead of the toml file's own directory, which is likely surprising and inconsistent with what script-based tasks in the same include directory do. The single-file toml path at line 2304 has the same characteristic, but the inconsistency becomes more visible now that both file types can coexist in one directory.

let root = Arc::new(root.to_path_buf());
let config_root = Arc::new(config_root.to_path_buf());
for path in files {
for path in exec_files {
let root = root.clone();
let config_root = config_root.clone();
let config = config.clone();
Expand Down
Loading