Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
- Fix: a PHP `use` import written with a leading-backslash / fully-qualified prefix now resolves to its target definition instead of being dropped (#2661, thanks @ousamabenyounes).
- Fix: an unresolved local JS/TS import (to a file absent from the scan) now emits a stable, portable `ref` target id instead of leaking a per-checkout absolute-path slug (#2457, thanks @rohit-jsfreaky).
- Fix: `graphify benchmark` no longer crashes on a node whose label is `None` (#2674, thanks @Arthuro0103).
- Fix: `graphify clone` now accepts multiple GitHub URLs and clones each one instead of silently dropping every URL past the first, matching the multi-repo workflow (#2703); unknown flags are rejected with a usage message, and `--out` is refused with multiple URLs to keep destinations unambiguous.

## 0.9.40 (2026-08-11)

Expand Down
38 changes: 28 additions & 10 deletions graphify/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2399,28 +2399,46 @@ def _to_simple(g: "_nx.Graph") -> "_nx.Graph":
print(f"Written to: {out_path}")

elif cmd == "clone":
_CLONE_USAGE = (
"Usage: graphify clone <github-url> [<github-url> ...] "
"[--branch <branch>] [--out <dir>]"
)
if len(sys.argv) < 3:
print(
"Usage: graphify clone <github-url> [--branch <branch>] [--out <dir>]",
file=sys.stderr,
)
print(_CLONE_USAGE, file=sys.stderr)
sys.exit(1)
url = sys.argv[2]
urls: list[str] = []
branch: str | None = None
out_dir: Path | None = None
args = sys.argv[3:]
args = sys.argv[2:]
i = 0
while i < len(args):
if args[i] == "--branch" and i + 1 < len(args):
a = args[i]
if a == "--branch" and i + 1 < len(args):
branch = args[i + 1]
i += 2
elif args[i] == "--out" and i + 1 < len(args):
elif a == "--out" and i + 1 < len(args):
out_dir = Path(args[i + 1])
i += 2
elif a.startswith("-"):
print(f"error: unknown option: {a}", file=sys.stderr)
print(_CLONE_USAGE, file=sys.stderr)
sys.exit(1)
else:
urls.append(a)
i += 1
local_path = _clone_repo(url, branch=branch, out_dir=out_dir)
print(local_path)
if not urls:
print(_CLONE_USAGE, file=sys.stderr)
sys.exit(1)
if out_dir is not None and len(urls) > 1:
print(
"error: --out is only valid with a single URL; drop --out to clone "
"multiple repos into ~/.graphify/repos/<owner>/<repo>",
file=sys.stderr,
)
sys.exit(1)
for url in urls:
local_path = _clone_repo(url, branch=branch, out_dir=out_dir)
print(local_path)

elif cmd == "export":
subcmd = sys.argv[2] if len(sys.argv) > 2 else ""
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/agents/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/amp/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/claude/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/claw/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/codex/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/copilot/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/droid/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/kilo/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/kiro/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/opencode/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/pi/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/trae/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/vscode/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
5 changes: 2 additions & 3 deletions graphify/skills/windows/references/github-and-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
110 changes: 110 additions & 0 deletions tests/test_clone_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""`graphify clone` multi-URL support (#2703).

The clone command used to silently swallow every URL after the first: a user
who typed `graphify clone url1 url2` got only url1 cloned and no indication
that url2 was dropped. Multi-repo analysis is the exact workflow issue #2703
asks about, so extra URLs are now cloned in turn and each destination path is
printed on its own line. Unknown flags are rejected instead of skipped.
"""
from __future__ import annotations

import sys
from pathlib import Path

import pytest

from graphify import cli


def _stub_clone(monkeypatch):
calls: list[tuple[str, str | None, Path | None]] = []

def fake(url, branch=None, out_dir=None):
calls.append((url, branch, out_dir))
return Path(f"/tmp/fake/{url.rsplit('/', 1)[-1]}")

monkeypatch.setattr(cli, "_clone_repo", fake)
return calls


def test_clone_multiple_urls_are_all_cloned(monkeypatch, capsys):
calls = _stub_clone(monkeypatch)
monkeypatch.setattr(
sys,
"argv",
[
"graphify",
"clone",
"https://github.com/foo/bar",
"https://github.com/baz/qux",
],
)
cli.dispatch_command("clone")
assert [c[0] for c in calls] == [
"https://github.com/foo/bar",
"https://github.com/baz/qux",
]
out = capsys.readouterr().out.splitlines()
assert "/tmp/fake/bar" in out
assert "/tmp/fake/qux" in out


def test_clone_single_url_still_works(monkeypatch, capsys):
calls = _stub_clone(monkeypatch)
monkeypatch.setattr(
sys, "argv", ["graphify", "clone", "https://github.com/foo/bar"]
)
cli.dispatch_command("clone")
assert calls == [("https://github.com/foo/bar", None, None)]
assert "/tmp/fake/bar" in capsys.readouterr().out


def test_clone_branch_flag_applies_to_all(monkeypatch):
calls = _stub_clone(monkeypatch)
monkeypatch.setattr(
sys,
"argv",
[
"graphify",
"clone",
"https://github.com/foo/bar",
"https://github.com/baz/qux",
"--branch",
"dev",
],
)
cli.dispatch_command("clone")
assert [c[1] for c in calls] == ["dev", "dev"]


def test_clone_rejects_out_with_multiple_urls(monkeypatch, capsys):
_stub_clone(monkeypatch)
monkeypatch.setattr(
sys,
"argv",
[
"graphify",
"clone",
"https://github.com/foo/bar",
"https://github.com/baz/qux",
"--out",
"/tmp/dest",
],
)
with pytest.raises(SystemExit) as exc:
cli.dispatch_command("clone")
assert exc.value.code == 1
assert "--out" in capsys.readouterr().err


def test_clone_rejects_unknown_flag(monkeypatch, capsys):
_stub_clone(monkeypatch)
monkeypatch.setattr(
sys,
"argv",
["graphify", "clone", "https://github.com/foo/bar", "--nope"],
)
with pytest.raises(SystemExit) as exc:
cli.dispatch_command("clone")
assert exc.value.code == 1
assert "--nope" in capsys.readouterr().err
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ LOCAL_PATH=$(graphify clone <github-url> [--branch <branch>])

**Multiple repos (cross-repo graph):**
```bash
# Clone each repo, run the full pipeline on each, then merge
graphify clone <url1> # → ~/.graphify/repos/<owner1>/<repo1>
graphify clone <url2> # → ~/.graphify/repos/<owner2>/<repo2>
# Clone each repo (one call or many — both work), run the full pipeline on each, then merge
graphify clone <url1> <url2> # → ~/.graphify/repos/<owner1>/<repo1> and .../<owner2>/<repo2>
# Run /graphify on each local path to produce their graph.json files
# Then merge:
graphify merge-graphs \
Expand Down
Loading
Loading