Repository navigation
fix(task): handle azure devops remote git includes - #11768
Conversation
📝 WalkthroughWalkthroughAzure DevOps SSH and HTTPS Git URLs now support organization, project, repository, refs, and task-file paths. Parsing rejects unsafe or incomplete paths. Remote task tests cover extraction and cache-key behavior. ChangesAzure DevOps Git support
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR extends remote Git task-source parsing to accept suffixless Azure DevOps repository URLs and adds parsing, path-safety, extraction, and cache-key tests.
Confidence Score: 4/5The Azure DevOps SSH path should be corrected before merging because real SSH clone URLs remain unrecognized by the remote task provider. The new HTTPS handling is covered, but the dedicated SSH regex expects the wrong repository path structure, so Azure DevOps task includes using SSH still fail provider selection. Files Needing Attention: src/remote_source.rs; src/task/task_file_providers/remote_task_git.rs Important Files Changed
Reviews (1): Last reviewed commit: "Run hk and fix issues" | Re-trigger Greptile |
| static AZURE_DEVOPS_SSH_GIT_REGEX: Lazy<Regex> = Lazy::new(|| { | ||
| Regex::new(r"^git::(?P<url>ssh://((?P<user>[^@]+)@)?(?P<host>[^/]+)/(?P<org>[^/]+)/(?P<project>[^/]+)/_git/(?P<repo>[^/]+))//(?P<path>[^?]+)(\?ref=(?P<ref>[^?&]+)(&.*)?)?$").unwrap() | ||
| }); |
There was a problem hiding this comment.
Azure SSH clone paths rejected
When a remote task uses an Azure DevOps SSH clone URL such as git::ssh://git@ssh.dev.azure.com/v3/org/project/repository//path/to/task, neither the generic .git-suffixed parser nor this new /_git/ parser recognizes it, causing task fetching to fail with “No provider found for file”.
Knowledge Base Used: Task Runner (mise run / mise tasks)
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/remote_source.rs`:
- Around line 8-10: Update RemoteSource::parse_git and its path validation to
reject traversal components represented with either forward or backslash
separators, including mixed-separator paths such as ..\plugin. Ensure the
validated or normalized repo_structure.path is confined before
RemoteTaskGit::get_local_path joins it with destination, while preserving valid
repository paths and references.
- Around line 8-10: Extend AZURE_DEVOPS_SSH_GIT_REGEX to accept the Azure DevOps
v3 SSH layout with the /v3/<org>/<project>/<repo> path while preserving existing
Azure DevOps URL support and captures for repository path and ref. Add a
regression test covering the documented ssh.dev.azure.com example, or explicitly
document that only the existing narrower URL contract is supported.
In `@src/task/task_file_providers/remote_task_git.rs`:
- Around line 322-324: Update the Azure HTTPS credential test case in the URL
fixture list to append a task path such as //myfile, ensuring parse_https
reaches credential rejection rather than failing earlier due to a missing task
path. Keep the existing user credential and assertion behavior unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 541d9e5b-d342-4627-afa7-ea98408d271d
📒 Files selected for processing (2)
src/remote_source.rssrc/task/task_file_providers/remote_task_git.rs
| static AZURE_DEVOPS_SSH_GIT_REGEX: Lazy<Regex> = Lazy::new(|| { | ||
| Regex::new(r"^git::(?P<url>ssh://((?P<user>[^@]+)@)?(?P<host>[^/]+)/(?P<org>[^/]+)/(?P<project>[^/]+)/_git/(?P<repo>[^/]+))//(?P<path>[^?]+)(\?ref=(?P<ref>[^?&]+)(&.*)?)?$").unwrap() | ||
| }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 6 'repo_structure\.path|full_path|destination\.join' \
src/task/task_file_providers/remote_task_git.rsRepository: jdx/mise
Length of output: 1612
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'remote_source\.rs|remote_task_git\.rs' . | sed 's#^\./##'
echo "== remote_source outline =="
if [ -f src/remote_source.rs ]; then
ast-grep outline src/remote_source.rs --match AzureDevOpsSshGitRegex --view expanded || true
wc -l src/remote_source.rs
sed -n '1,90p' src/remote_source.rs
fi
echo "== remote_task_git outline and relevant sections =="
if [ -f src/task/task_file_providers/remote_task_git.rs ]; then
wc -l src/task/task_file_providers/remote_task_git.rs
ast-grep outline src/task/task_file_providers/remote_task_git.rs --view expanded || true
sed -n '1,230p' src/task/task_file_providers/remote_task_git.rs
fi
echo "== search for path validation / confinement patterns =="
rg -n -C 4 'sanitize|normalize|RUSTSEC-2020-0071|relative|canonical|path\(|backslash|\\\\|validate|repo_file_path|full_path|storage_path' src/task src/remote_source.rs 2>/dev/null || trueRepository: jdx/mise
Length of output: 50365
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== exact target file sizes =="
wc -l src/remote_source.rs src/task/task_file_providers/remote_task_git.rs
echo "== src/remote_source.rs outline =="
ast-grep outline src/remote_source.rs --view expanded
echo
echo "== target regex and parse sections =="
rg -n -C 8 'AZURE_(DEVOPS_SSH_GIT_REGEX|DEVOPS_HTTP_GIT_REGEX)|fn parse|regex|matches' src/remote_source.rs
echo
echo "== exact target path construction/validation sections =="
rg -n -C 12 'get_repo_structure|repo_structure|destination\.join|path\.components|validate|split\(|full_path|get_local_path' src/task/task_file_providers/remote_task_git.rs
echo
echo "== focused path validation search =="
rg -n -C 4 'components\.any|PathBuf::from|\.to_path_buf\(|\\\.join\(|RUSTSEC-2020-0071|normalize|canonicalize|contains\(\[.*\\]' src/task/task_file_providers/remote_task_git.rsRepository: jdx/mise
Length of output: 13530
Reject backslash path traversal before joining repo_structure.path.
RemoteSource::parse_git only checks / components, so git::ssh://.../_git/repo//..\\plugin?ref=master can pass and RemoteTaskGit::get_local_path joins the whole repo_structure.path into destination on Windows. Add a parser-level path validator that rejects .. with / or \, or normalize and confine the path before destination.join(&repo_file_path).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/remote_source.rs` around lines 8 - 10, Update RemoteSource::parse_git and
its path validation to reject traversal components represented with either
forward or backslash separators, including mixed-separator paths such as
..\plugin. Ensure the validated or normalized repo_structure.path is confined
before RemoteTaskGit::get_local_path joins it with destination, while preserving
valid repository paths and references.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files =="
git ls-files | sed -n '1,120p'
echo "== locate remote_source =="
fd -a 'remote_source\.rs$' . || true
echo "== search Azure regex and parse_git_with =="
rg -n "AZURE_DEVOPS_SSH_GIT_REGEX|AZURE_DEVOPS|parse_git_with|_git|_git/(?P<repo>|\\([^/\\]\\+\\))|v3|azure.com" . || true
echo "== file outline if present =="
if [ -f src/remote_source.rs ]; then
ast-grep outline src/remote_source.rs || true
echo "== src/remote_source.rs sample =="
cat -n src/remote_source.rs | sed -n '1,220p'
fiRepository: jdx/mise
Length of output: 14229
🌐 Web query:
Azure DevOps use SSH keys to authenticate Git SSH remote URL format ssh.dev.azure.com v3
💡 Result:
To authenticate with Azure DevOps using SSH, you must use the ssh.dev.azure.com hostname [1][2]. The standard format for a Git SSH remote URL in Azure DevOps is as follows: git@ssh.dev.azure.com:v3/{organization}/{project}/{repository} [3][4][5] Key details regarding this configuration include: 1. URL Structure: The URL includes a v3 segment, which is required for the pathing of Azure DevOps organizations, projects, and repositories [3][4][2]. 2. Setup Process: - You must generate an SSH key pair on your local machine and add the public key to your Azure DevOps user profile via the web portal (User Settings > SSH Public Keys) [1][3]. - If using a custom SSH key filename, it is highly recommended to configure your ~/.ssh/config file to map the host to the specific identity file to ensure the correct key is used [4][5][6]: Host ssh.dev.azure.com HostName ssh.dev.azure.com User git IdentityFile ~/.ssh/your_private_key IdentitiesOnly yes 3. Updating Remotes: If you have existing repositories using the older vs-ssh.visualstudio.com format, you can update them by running git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository} [1][2]. 4. Troubleshooting: You can verify your connection and key authentication by running the command ssh -T git@ssh.dev.azure.com [3][4][5]. If authentication fails, ensure your SSH agent has the key loaded or that your ~/.ssh/config file is correctly configured [4][6]. For self-hosted Azure DevOps Server instances, use the specific server hostname instead of ssh.dev.azure.com in both the URL and your SSH configuration [1][2].
Citations:
- 1: https://learn.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate?view=azure-devops
- 2: https://github.com/MicrosoftDocs/azure-devops-docs/blob/main/docs/repos/git/use-ssh-keys-to-authenticate.md
- 3: https://stackoverflow.com/questions/77683284/how-to-clone-a-repo-with-a-azure-devops-link-and-an-ssh-key
- 4: https://benjamin-abt.com/blog/2025/08/20/azure-devops-use-git-ssh/
- 5: https://blog.serialexperiments.co.uk/posts/git-ssh-azure-repo/
- 6: https://stackoverflow.com/questions/52985167/git-ssh-public-key-authentication-failed-with-git-on-azure-devops
🏁 Script executed:
#!/bin/bash
set -euo pipefail
cat -n src/remote_source.rs | sed -n '220,320p'
python3 - <<'PY'
import re
pattern = re.compile(r"^git::(?P<url>ssh://((?P<user>[^@]+)@)?(?P<host>[^/]+)/(?P<org>[^/]+)/(?P<project>[^/]+)/_git/(?P<repo>[^/]+))//(?P<path>[^?]+)(\?ref=(?P<ref>[^?&]+)(&.*)?)?$")
for url in [
"git::ssh://git@dev.azure/myorg/myproj/_git/example//terraform/myfile?ref=master",
"git::ssh://git@ssh.dev.azure.com/v3/orG/prOj/ecH/example//terraform/myfile?ref=master",
"git::ssh://git@ssh.dev.azure.com/g/v3/orG/prOj/ecH/example//terraform/myfile?ref=master",
]:
m = pattern.match(url)
print(f"url={url!r}")
print(" match=", bool(m))
if m:
print(" url=", m.group("url"))
print(" path=", m.group("path"))
print(" ref=", m.group("ref"))
PYRepository: jdx/mise
Length of output: 1390
Support the Azure DevOps v3 SSH layout.
Azure DevOps SSH remotes use ssh.dev.azure.com/v3/<org>/<project>/<repo> with paths like git::ssh://git@ssh.dev.azure.com/v3/orG/prOj/ecH/example//terraform/myfile?ref=master. The current regex only accepts /org/<project>/_git/<repo>, so add this layout and a regression test, or document the narrower URL contract.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/remote_source.rs` around lines 8 - 10, Extend AZURE_DEVOPS_SSH_GIT_REGEX
to accept the Azure DevOps v3 SSH layout with the /v3/<org>/<project>/<repo>
path while preserving existing Azure DevOps URL support and captures for
repository path and ref. Add a regression test covering the documented
ssh.dev.azure.com example, or explicitly document that only the existing
narrower URL contract is supported.
| "git::https://dev.azure/org/project/_git/example?ref=master", | ||
| "git::https://user@dev.azure/org/project/_git/example", | ||
| "git::ssh://git@dev.azure/org/project/_git/example//myfile?ref=v1.0.0", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant Rust source and tests around the referenced lines.
if [ -f src/task/task_file_providers/remote_task_git.rs ]; then
echo "== file exists =="
wc -l src/task/task_file_providers/remote_task_git.rs
echo "== outline =="
ast-grep outline src/task/task_file_providers/remote_task_git.rs --view compact || true
echo "== lines 280-345 =="
sed -n '280,345p' src/task/task_file_providers/remote_task_git.rs | nl -ba -v280
else
echo "== file missing =="
fd -a 'remote_task_git.rs' .
fi
echo "== search parse_https and credential-related tests/code =="
rg -n "parse_https|credential|user@|test|assert|invalid" src/task/task_file_providers/remote_task_git.rs src || trueRepository: jdx/mise
Length of output: 442
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file stats =="
wc -l src/task/task_file_providers/remote_task_git.rs
echo "== ast outline expanded/main sections =="
ast-grep outline src/task/task_file_providers/remote_task_git.rs --view signatures || true
echo "== lines 280-345 with line numbers =="
awk 'NR>=280 && NR<=345 {printf "%5d\t%s\n", NR, $0}' src/task/task_file_providers/remote_task_git.rs
echo "== search parse_https and credential-related code/tests =="
rg -n "parse_https|credential|user@|test|assert" src/task/task_file_providers/remote_task_git.rs || true
echo "== deterministic URL path split check from source text =="
python3 - <<'PY'
from pathlib import Path
s = Path('src/task/task_file_providers/remote_task_git.rs').read_text()
for needle in [
'fn parse_https',
'git::https://user@dev.azure/org/project/_git/example',
'git::ssh://git@dev.azure/org/project/_git/example//myfile?ref=v1.0.0'
]:
print(needle, 'present=', needle in s)
if needle in s:
idx = s.index(needle)
start=max(0, idx-800); end=min(len(s), idx+800)
print("context:")
for i, line in enumerate(s[start:].splitlines(), max(1, s[:start].count('\n')+1)):
if idx <= start + sum(len(l)+1 for l in s[start:].splitlines()[:i-1]) < idx+800:
pass
print(f"{i}\t{line}")
PYRepository: jdx/mise
Length of output: 47400
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate RemoteSource implementation =="
rg -n "struct RemoteSource|impl RemoteSource|parse_git_https|parse_git_ssh|credential|https://user|ssh://user" --glob '*.rs' . || true
echo "== read likely files around RemoteSource =="
while IFS= read -r line; do
file="${line##*:}"
echo "--- $file"
wc -l "$file"
ast-grep outline "$file" --view signatures || true
sed -n '1,520p' "$file" | awk 'NR>=1 && NR<=520 {printf "%5d\t%s\n", NR, $0}'
done < <(rg -l "parse_git_https|parse_git_ssh" --glob '*.rs' .)Repository: jdx/mise
Length of output: 21165
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== src/remote_source.rs =="
wc -l src/remote_source.rs
sed -n '1,150p' src/remote_source.rs | awk 'NR>=1 && NR<=150 {printf "%5d\t%s\n", NR, $0}'
echo "== src/remote_source.rs tests around credential cases =="
sed -n '110,220p' src/remote_source.rs | awk 'NR>=1 && NR<=110 {printf "%5d\t%s\n", NR+109, $0}'Repository: jdx/mise
Length of output: 10870
Add an Azure HTTPS credential case with a //<task-path>.
git::https://user@dev.azure/org/project/_git/example fails parsing before the credential condition because there is no task path. Add //myfile so assert!(parse_https(...).is_none()) exercises credential rejection when HTTPS supports a task path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/task/task_file_providers/remote_task_git.rs` around lines 322 - 324,
Update the Azure HTTPS credential test case in the URL fixture list to append a
task path such as //myfile, ensuring parse_https reaches credential rejection
rather than failing earlier due to a missing task path. Keep the existing user
credential and assertion behavior unchanged.
|
wouldn't be microsoft if it wasn't both different and worse |
Azure Devops uses a different URL scheme for git repositories than most other git servers. It looks something like https://dev.azure/organisation/project/_git/repository, which is typically not suffixed by
.git(unless the literal repo name ends with.git). Mise currently expects the url to end with.gitso naturally these repositories do not work as remote git sources for tasks. I wrote #11660 where I tried demonstrating this.I opted for adding a second regex for matching the azure devops url pattern, felt easier to get it right than modifying the existing one to match both patterns.
I've built and tested this against my company instance of Azure Devops. I still have some issues getting the git include feature to fully work. One issue regarding pack files when using ssh (issue has been patched in next version of gitoxide, whenever that drops) and some issues with https auth which I have not figured out yet (likely also some issue with gitoxide though).
Summary by CodeRabbit
New Features
Bug Fixes