Skip to content

fix(task): handle azure devops remote git includes - #11768

Merged
jdx merged 4 commits into
jdx:mainfrom
cheesemans:main
Aug 7, 2026
Merged

jdx merged 4 commits into
jdx:mainfrom
cheesemans:main

Conversation

@cheesemans

@cheesemans cheesemans commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

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 .git so 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

    • Added support for Azure DevOps Git repositories using SSH and HTTPS URLs.
    • Recognizes organization, project, repository, branch, credentials, ports, and task-file paths.
    • Supports common URL variations, including additional query parameters and optional SSH users.
  • Bug Fixes

    • Improved Git source parsing for Azure DevOps repositories.
    • Prevents invalid or incomplete repository and task-file paths from being accepted.
    • Ensures repository caching distinguishes branches while reusing entries across task files.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Azure 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.

Changes

Azure DevOps Git support

Layer / File(s) Summary
Azure DevOps URL parsing
src/remote_source.rs
Adds Azure DevOps SSH and HTTPS patterns. Supports optional users, ports, refs, and query parameters. Rejects missing or unsafe paths.
Remote task Git integration
src/task/task_file_providers/remote_task_git.rs
Adds Azure DevOps parsing and extraction coverage. Verifies that refs affect cache keys while task-file paths do not.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • jdx/mise#11203: Both changes cover Git URL parsing and cache-key behavior in remote_task_git.rs.

Suggested reviewers: jdx, risu729

Poem

A rabbit hops through SSH,
Then nibbles HTTPS paths.
Azure refs mark every trail,
While cache keys hold their place.
Safe paths pass; unsafe ones fail.
Hop, hop, merge! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding Azure DevOps remote Git include support for tasks.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends remote Git task-source parsing to accept suffixless Azure DevOps repository URLs and adds parsing, path-safety, extraction, and cache-key tests.

  • Adds dedicated Azure-shaped SSH and HTTP(S) regular expressions.
  • Reuses existing path traversal validation and ref extraction.
  • Expands RemoteTaskGit tests for Azure URL matching and cache behavior.

Confidence Score: 4/5

The 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

Filename Overview
src/remote_source.rs Adds Azure DevOps URL parsing, but the SSH expression models the HTTPS /_git/ form and misses Azure SSH /v3/ clone URLs.
src/task/task_file_providers/remote_task_git.rs Adds Azure parsing and cache-key coverage, although its SSH fixtures reinforce the unsupported URL shape.

Reviews (1): Last reviewed commit: "Run hk and fix issues" | Re-trigger Greptile

Comment thread src/remote_source.rs
Comment on lines +8 to +10
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()
});

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.

P1 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)

@coderabbitai coderabbitai Bot left a comment

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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e6d9aed and b80eb29.

📒 Files selected for processing (2)
  • src/remote_source.rs
  • src/task/task_file_providers/remote_task_git.rs

Comment thread src/remote_source.rs
Comment on lines +8 to +10
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()
});

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.

🔒 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.rs

Repository: 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 || true

Repository: 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.rs

Repository: 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'
fi

Repository: 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:


🏁 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"))
PY

Repository: 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.

Comment on lines +322 to +324
"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",

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.

🎯 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 || true

Repository: 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}")
PY

Repository: 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.

@jdx
jdx merged commit 960ec4c into jdx:main Aug 7, 2026
30 checks passed
@jdx

jdx commented Aug 7, 2026

Copy link
Copy Markdown
Owner

wouldn't be microsoft if it wasn't both different and worse

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants