@@ -53,6 +53,138 @@ def _patch_git_config(name, value): |
53 | 53 | yield |
54 | 54 |
|
55 | 55 |
|
| 56 | +@pytest.mark.parametrize("state", ["unborn", "untracked", "detached"]) |
| 57 | +@pytest.mark.parametrize("remote", [None, ".", "origin"], ids=["working-tree", "local-origin", "origin"]) |
| 58 | +def test_add_relative_submodule_without_tracking_remote(tmp_path, state, remote): |
| 59 | + with git.Repo.init(tmp_path / "parent") as parent: |
| 60 | + if state != "unborn": |
| 61 | + parent.index.commit("Initial commit") |
| 62 | + if state == "detached": |
| 63 | + parent.head.reference = parent.head.commit |
| 64 | + base = tmp_path / "remote" if remote == "origin" else tmp_path / "parent" |
| 65 | + if remote is not None: |
| 66 | + parent.create_remote("origin", str(base) if remote == "origin" else ".") |
| 67 | + with git.Repo.init(base / "source") as source: |
| 68 | + source.git.symbolic_ref("HEAD", "refs/heads/master") |
| 69 | + source.index.commit("Initial commit") |
| 70 | + submodule = parent.create_submodule("module", "module", "./source") |
| 71 | + assert submodule.url == "./source" |
| 72 | + with submodule.module() as module: |
| 73 | + assert module.head.commit == source.head.commit |
| 74 | + assert Path(module.remotes.origin.url).samefile(source.working_tree_dir) |
| 75 | + parent.index.commit("Add submodule") |
| 76 | + submodule.remove(configuration=False) |
| 77 | + submodule.update(init=True) |
| 78 | + with submodule.module() as module: |
| 79 | + assert module.head.commit == source.head.commit |
| 80 | + assert Path(module.remotes.origin.url).samefile(source.working_tree_dir) |
| 81 | + |
| 82 | + |
| 83 | +@pytest.mark.parametrize("url", ["./source", "../source", "./../source"]) |
| 84 | +def test_add_preserves_relative_url_on_cygwin(tmp_path, url): |
| 85 | + with git.Repo.init(tmp_path / "parent") as parent, git.Repo.init(tmp_path / "source") as source: |
| 86 | + source.index.commit("Initial commit") |
| 87 | + with mock.patch.object(Submodule, "_clone_repo", return_value=source): |
| 88 | + with mock.patch.object(Git, "is_cygwin", return_value=True): |
| 89 | + submodule = parent.create_submodule("module", "module", url) |
| 90 | + assert submodule.url == url |
| 91 | + with submodule.config_reader() as reader: |
| 92 | + assert reader.get_value("url") == url |
| 93 | + with parent.config_reader() as reader: |
| 94 | + assert reader.get_value('submodule "module"', "url") == url |
| 95 | + |
| 96 | + |
| 97 | +def test_update_resolves_local_relative_remote_once(tmp_path): |
| 98 | + with git.Repo.init(tmp_path / "parent") as parent, git.Repo.init(tmp_path / "repos" / "child.git") as source: |
| 99 | + source.git.symbolic_ref("HEAD", "refs/heads/master") |
| 100 | + source.index.commit("Initial commit") |
| 101 | + parent.create_remote("origin", "../repos/super.git") |
| 102 | + parent.head.reference.set_tracking_branch(git.RemoteReference(parent, "refs/remotes/origin/master")) |
| 103 | + submodule = Submodule( |
| 104 | + parent, |
| 105 | + source.head.commit.binsha, |
| 106 | + name="module", |
| 107 | + path="module", |
| 108 | + url="../child.git", |
| 109 | + branch_path="refs/heads/master", |
| 110 | + ) |
| 111 | + submodule.update(init=True) |
| 112 | + with submodule.module() as module: |
| 113 | + assert module.head.commit == source.head.commit |
| 114 | + assert Path(module.remotes.origin.url).samefile(source.working_tree_dir) |
| 115 | + module.remotes.origin.fetch() |
| 116 | + |
| 117 | + |
| 118 | +@pytest.mark.parametrize("no_fetch", [False, True]) |
| 119 | +def test_root_update_anchors_relative_remote_url(tmp_path, no_fetch): |
| 120 | + with git.Repo.init(tmp_path / "repos" / "source.git") as source, git.Repo.init(tmp_path / "parent") as parent: |
| 121 | + source.git.symbolic_ref("HEAD", "refs/heads/master") |
| 122 | + source.index.commit("Initial commit") |
| 123 | + with git.Repo.clone_from(source.working_tree_dir, tmp_path / "repos" / "replacement.git") as replacement: |
| 124 | + parent.create_remote("origin", "../repos/super.git") |
| 125 | + submodule = parent.create_submodule("module", "module", source.working_tree_dir) |
| 126 | + with submodule.config_writer() as writer: |
| 127 | + writer.set_value("url", "../source.git") |
| 128 | + previous = parent.index.commit("Record relative URL") |
| 129 | + with submodule.config_writer() as writer: |
| 130 | + writer.set_value("url", "../replacement.git") |
| 131 | + parent.index.commit("Change relative URL") |
| 132 | + |
| 133 | + RootModule(parent).update(previous_commit=previous, no_fetch=no_fetch) |
| 134 | + |
| 135 | + with submodule.module() as module: |
| 136 | + assert Path(module.remotes.origin.url).samefile(replacement.working_tree_dir) |
| 137 | + assert module.head.commit == replacement.head.commit |
| 138 | + module.remotes.origin.fetch() |
| 139 | + |
| 140 | + |
| 141 | +@pytest.mark.parametrize("no_fetch", [False, True]) |
| 142 | +def test_root_update_uses_single_remote_without_resolving_old_url(tmp_path, no_fetch): |
| 143 | + with git.Repo.init(tmp_path / "source") as source, git.Repo.init(tmp_path / "parent") as parent: |
| 144 | + source.git.symbolic_ref("HEAD", "refs/heads/master") |
| 145 | + source.index.commit("Initial commit") |
| 146 | + with git.Repo.clone_from(source.working_tree_dir, tmp_path / "replacement") as replacement: |
| 147 | + parent.create_remote("origin", "../super.git") |
| 148 | + submodule = parent.create_submodule("module", "module", source.working_tree_dir) |
| 149 | + with submodule.config_writer() as writer: |
| 150 | + writer.set_value("url", "../../lib.git") |
| 151 | + previous = parent.index.commit("Record historical URL") |
| 152 | + with submodule.config_writer() as writer: |
| 153 | + writer.set_value("url", replacement.working_tree_dir) |
| 154 | + parent.index.commit("Replace historical URL") |
| 155 | + |
| 156 | + RootModule(parent).update(previous_commit=previous, no_fetch=no_fetch) |
| 157 | + |
| 158 | + with submodule.module() as module: |
| 159 | + assert Path(module.remotes.origin.url).samefile(replacement.working_tree_dir) |
| 160 | + assert module.head.commit == replacement.head.commit |
| 161 | + |
| 162 | + |
| 163 | +@pytest.mark.parametrize("dry_run", [False, True]) |
| 164 | +def test_root_update_skips_old_url_resolution_when_remote_matches(tmp_path, dry_run): |
| 165 | + with git.Repo.init(tmp_path / "source") as source, git.Repo.init(tmp_path / "parent") as parent: |
| 166 | + source.index.commit("Initial commit") |
| 167 | + submodule = parent.create_submodule("module", "module", source.working_tree_dir) |
| 168 | + with submodule.config_writer() as writer: |
| 169 | + writer.set_value("url", "../source") |
| 170 | + previous = parent.index.commit("Record relative submodule URL") |
| 171 | + with submodule.config_writer() as writer: |
| 172 | + writer.set_value("url", Git.polish_url(source.working_tree_dir)) |
| 173 | + parent.index.commit("Use existing remote URL") |
| 174 | + |
| 175 | + resolve_url = Submodule._resolved_url |
| 176 | + |
| 177 | + def resolve_current_url(sm): |
| 178 | + assert sm.url != "../source", "Previous URL does not need resolving" |
| 179 | + return resolve_url(sm) |
| 180 | + |
| 181 | + with mock.patch.object(Submodule, "_resolved_url", autospec=True, side_effect=resolve_current_url): |
| 182 | + RootModule(parent).update(previous_commit=previous, no_fetch=True, dry_run=dry_run) |
| 183 | + with submodule.module() as module: |
| 184 | + assert module.remotes.origin.url == Git.polish_url(source.working_tree_dir) |
| 185 | + assert module.head.commit == source.head.commit |
| 186 | + |
| 187 | + |
56 | 188 | @pytest.mark.parametrize( |
57 | 189 | "name", ["module", "$GITPYTHON_TEST_SECRET", "prefix-${GITPYTHON_TEST_SECRET}-suffix", "%GITPYTHON_TEST_SECRET%"] |
58 | 190 | ) |
@@ -2437,6 +2569,9 @@ def test_resolve_relative_url_like_git(self): |
2437 | 2569 | ("//share/super", "../../sub", "//sub"), |
2438 | 2570 | ("foo/bar", "../sub", "foo/sub"), |
2439 | 2571 | ("./foo", "../sub", "sub"), |
| 2572 | + ("./super.git", "../", ""), |
| 2573 | + ("ssh://host/org/super.git", "./", "ssh://host/org/super.git/"), |
| 2574 | + ("ssh://host/org/super.git", "../", "ssh://host/org/"), |
2440 | 2575 | ("../foo/bar", "../sub", "../foo/sub"), |
2441 | 2576 | ("ssh://host/org/super.git", ".sub", ".sub"), |
2442 | 2577 | ("ssh://host/org/super.git", "https://other/sub", "https://other/sub"), |
@@ -2459,23 +2594,27 @@ def test_update_resolves_relative_url_against_remote(self, rwdir): |
2459 | 2594 | ("../planted", "ssh://git@host/org/planted"), |
2460 | 2595 | ]: |
2461 | 2596 | submodule = Submodule(parent, Submodule.NULL_BIN_SHA, name="module", path="module", url=url) |
2462 | | - with mock.patch.object(Submodule, "_clone_repo", side_effect=AssertionError("stop")) as clone_repo: |
| 2597 | + with mock.patch.object(git.Repo, "clone_from", side_effect=AssertionError("stop")) as clone_from: |
2463 | 2598 | with pytest.raises(AssertionError, match="stop"): |
2464 | 2599 | submodule.update(init=True) |
2465 | | - assert clone_repo.call_args.args[1] == expected |
| 2600 | + assert clone_from.call_args.args[0] == expected |
2466 | 2601 |
|
2467 | 2602 | @with_rw_directory |
2468 | 2603 | def test_clone_repo_resolves_relative_url_against_tracking_remote(self, rwdir): |
2469 | 2604 | parent = git.Repo.init(osp.join(rwdir, "parent")) |
2470 | 2605 | parent.index.commit("initial") |
2471 | | - parent.create_remote("origin", "git@host:org/super.git") |
2472 | | - parent.head.reference.set_tracking_branch(git.RemoteReference(parent, "refs/remotes/origin/main")) |
2473 | | - |
2474 | | - for url, expected in [ |
2475 | | - ("./planted", "git@host:org/super.git/planted"), |
2476 | | - ("../planted", "git@host:org/planted"), |
2477 | | - ]: |
2478 | | - with mock.patch.object(git.Repo, "clone_from", side_effect=AssertionError("stop")) as clone_from: |
2479 | | - with pytest.raises(AssertionError, match="stop"): |
2480 | | - Submodule._clone_repo(parent, url, "module", "module") |
2481 | | - assert clone_from.call_args.args[0] == expected |
| 2606 | + parent.create_remote("origin", "git@host:other/super.git") |
| 2607 | + for remote_name in ("upstream", "team/upstream"): |
| 2608 | + parent.create_remote(remote_name, "git@host:org/super.git") |
| 2609 | + with parent.head.reference.config_writer() as writer: |
| 2610 | + writer.set_value("remote", remote_name) |
| 2611 | + writer.set_value("merge", "refs/heads/main") |
| 2612 | + |
| 2613 | + for url, expected in [ |
| 2614 | + ("./planted", "git@host:org/super.git/planted"), |
| 2615 | + ("../planted", "git@host:org/planted"), |
| 2616 | + ]: |
| 2617 | + with mock.patch.object(git.Repo, "clone_from", side_effect=AssertionError("stop")) as clone_from: |
| 2618 | + with pytest.raises(AssertionError, match="stop"): |
| 2619 | + Submodule._clone_repo(parent, url, "module", "module") |
| 2620 | + assert clone_from.call_args.args[0] == expected |