diff --git a/src/grimoire/mcp/tools/repos.py b/src/grimoire/mcp/tools/repos.py index a63faf39..c1d86c75 100644 --- a/src/grimoire/mcp/tools/repos.py +++ b/src/grimoire/mcp/tools/repos.py @@ -34,8 +34,10 @@ async def bind_repo( under the old one. Pass the old remote as `moved_from` and the binding, every shape judgment, and every snippet location recorded in the old repo move to the new name together. Binding the new name without it starts an - empty ledger beside the old one. A snippet shared with you read-only keeps - its location and is listed in `snippets_read_only`; its owner moves it. + empty ledger beside the old one. Running it again once the binding has + moved moves only what still names the old repo. A snippet shared with you + read-only keeps its location and is listed in `snippets_read_only`; its + owner moves it. Args: repo_url: the repo's git remote (e.g. the output of @@ -54,9 +56,16 @@ async def bind_repo( ref_arg = None if not ref else ("" if ref.strip() == "-" else ref) moved = snippets = None if moved_from: - _, moved = await repo_bindings_svc.move_binding( - uid, moved_from, repo_url, project_id, + already = ( + await repo_bindings_svc.resolve_project(uid, moved_from) is None + and await repo_bindings_svc.resolve_project(uid, repo_url) == project_id ) + if already: + moved = 0 + else: + _, moved = await repo_bindings_svc.move_binding( + uid, moved_from, repo_url, project_id, + ) snippets = await snippets_svc.move_location_repo( uid, project_id, moved_from, repo_url, ) diff --git a/tests/test_integration_repo_move.py b/tests/test_integration_repo_move.py index 700ddad8..ee102a19 100644 --- a/tests/test_integration_repo_move.py +++ b/tests/test_integration_repo_move.py @@ -112,7 +112,12 @@ async def test_the_snippets_recorded_in_the_old_repo_move_with_it(bound): note = await snippets_svc.create_snippet( owner, name=name, code=f"def {name}():\n pass\n", language="python", project_id=pid, - locations=[{"repo": r, "path": "src/app.py", "symbol": name} for r in repos], + # A path per location: rewritten to one repo, identical locations + # would merge into one. + locations=[ + {"repo": r, "path": f"src/app{i}.py", "symbol": name} + for i, r in enumerate(repos) + ], ) return int(note.id) @@ -132,3 +137,29 @@ async def test_the_snippets_recorded_in_the_old_repo_move_with_it(bound): assert await repos(moved_one) == [NEW_KEY, NEW_KEY, NEW_KEY] assert await repos(mixed) == [NEW_KEY, "elsewhere/gadget"] assert await repos(untouched) == ["Gadget", "git.example.com/alice/oldnamesake"] + + +@pytest.mark.integration +async def test_running_the_move_again_finishes_what_an_earlier_one_left(bound): + from grimoire.mcp._context import _user_id_ctx + from grimoire.mcp.tools.repos import bind_repo + from grimoire.services import snippets as snippets_svc + + owner, pid = bound["owner"], bound["pid"] + await svc.move_binding(owner, OLD, NEW, pid) + note = await snippets_svc.create_snippet( + owner, name="left_behind", code="def left_behind():\n pass\n", + language="python", project_id=pid, + locations=[{"repo": "OldName", "path": "src/app.py", "symbol": "left_behind"}], + ) + + token = _user_id_ctx.set(owner) + try: + out = await bind_repo(NEW, pid, moved_from=OLD) + finally: + _user_id_ctx.reset(token) + + assert out["shapes_moved"] == 0 + assert out["snippets_moved"] == [int(note.id)] + moved = await snippets_svc.get_snippet(owner, int(note.id)) + assert snippets_svc.snippet_fields(moved)["locations"][0]["repo"] == NEW_KEY