Skip to content

ADO: full https:// URL with sub-path rejected by parser (shorthand and object form work) #1128

@danielmeppiel

Description

@danielmeppiel

Summary

DependencyReference.parse() rejects the natural full-URL form for an Azure DevOps repository with a sub-path:

https://dev.azure.com/<org>/<project>/_git/<repo>/<sub-path>

This is the URL ADO's browser shows. Users paste it straight into apm.yml and hit ValueError: Invalid Azure DevOps repository path. The shorthand form (dev.azure.com/<org>/<project>/_git/<repo>/<sub-path>) and the object form (git: <url> + path: <sub-path>) both work for the same dependency, so the failure mode is "the most natural URL is the only one that doesn't work."

Reproduction

from apm_cli.models.dependency.reference import DependencyReference

# WORKS (shorthand)
DependencyReference.parse("dev.azure.com/myorg/myproj/_git/myrepo/instructions/security#v2.0")

# WORKS (object form)
DependencyReference.parse_from_dict({
    "git": "https://dev.azure.com/myorg/myproj/_git/myrepo",
    "path": "instructions/security",
    "ref": "v2.0",
})

# FAILS - this is what users paste from their ADO browser tab
DependencyReference.parse("https://dev.azure.com/myorg/myproj/_git/myrepo/instructions/security")
# ValueError: Invalid Azure DevOps repository path: expected 'org/project/repo',
#   got 'myorg/myproj/_git/myrepo/instructions/security'

# Same failure with #ref appended
DependencyReference.parse("https://dev.azure.com/myorg/myproj/_git/myrepo/instructions/security#v2.0")

Full matrix exercised locally on main:

Shape Result
dev.azure.com/<o>/<p>/_git/<r> OK
dev.azure.com/.../<r>#v2.0 OK
dev.azure.com/.../<r>/<sub> OK
dev.azure.com/.../<r>/<sub>#v2.0 OK
https://dev.azure.com/<o>/<p>/_git/<r> OK
https://dev.azure.com/.../<r>#v2.0 OK
https://dev.azure.com/.../<r>/<sub> FAIL
https://dev.azure.com/.../<r>/<sub>#v2.0 FAIL
Object form (any combination) OK

Root cause (likely)

src/apm_cli/models/dependency/reference.py:602-610 strips the _git segment for ADO and then asserts a 3-segment path (org/project/repo). When the path includes a sub-path, the post-strip segment list is longer than 3 and the strict check fires before the virtual-path detection that handles the same case in the shorthand branch.

The shorthand branch handles this correctly (the OK rows above). The full-URL branch needs to either (a) defer the 3-segment check until after virtual-path detection, or (b) reuse whatever logic the shorthand branch already has.

Acceptance criteria

Failing test (repro, ready to add to the suite)

import pytest
from apm_cli.models.dependency.reference import DependencyReference


@pytest.mark.parametrize("dep_str,expected_subpath,expected_ref", [
    # Shorthand - all currently pass
    ("dev.azure.com/myorg/myproj/_git/myrepo", None, None),
    ("dev.azure.com/myorg/myproj/_git/myrepo#v2.0", None, "v2.0"),
    ("dev.azure.com/myorg/myproj/_git/myrepo/instructions/security", "instructions/security", None),
    ("dev.azure.com/myorg/myproj/_git/myrepo/instructions/security#v2.0", "instructions/security", "v2.0"),
    # Full https - first two pass, last two FAIL today (this issue)
    ("https://dev.azure.com/myorg/myproj/_git/myrepo", None, None),
    ("https://dev.azure.com/myorg/myproj/_git/myrepo#v2.0", None, "v2.0"),
    ("https://dev.azure.com/myorg/myproj/_git/myrepo/instructions/security", "instructions/security", None),
    ("https://dev.azure.com/myorg/myproj/_git/myrepo/instructions/security#v2.0", "instructions/security", "v2.0"),
])
def test_ado_subpath_ref_combinations(dep_str, expected_subpath, expected_ref):
    dep = DependencyReference.parse(dep_str)
    assert dep.ado_organization == "myorg"
    assert dep.ado_project == "myproj"
    assert dep.ado_repo == "myrepo"
    assert dep.virtual_path == expected_subpath
    assert dep.is_virtual == (expected_subpath is not None)
    assert dep.reference == expected_ref

The first 6 rows pass on main. The last 2 rows fail with ValueError: Invalid Azure DevOps repository path.

User impact

Surfaced via user feedback on agent-asset distribution. They reported the dependency "could not get it to work (failed)" - this URL form is the most likely cause given how ADO's UI presents repo URLs. Workarounds exist (shorthand, object form) and are now documented in #1127, but the natural pasted URL should also work.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/dependenciesarea/docs-sitedocs/src/content (Starlight), README, doc generation.area/lockfileLockfile schema, per-file provenance, integrity hashes, drift detection.bugDeprecated: use type/bug. Kept for issue history; will be removed in milestone 0.10.0.priority/highShips in current or next milestonestatus/acceptedDirection approved, safe to start work.status/triagedInitial agentic triage complete; pending maintainer ratification (silence = approval).theme/portabilityOne manifest, every target. Multi-target deploy, marketplace, packaging, install.type/bugSomething does not work as documented.

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions