def test_import_replace_existing_with_column_renames( data_archive, tmp_path, cli_runner, chdir, ): with data_archive("gpkg-polygons") as data: repo_path = tmp_path / "emptydir" r = cli_runner.invoke(["init", repo_path]) assert r.exit_code == 0 with chdir(repo_path): r = cli_runner.invoke([ "import", data / "nz-waca-adjustments.gpkg", "nz_waca_adjustments:mytable", ]) assert r.exit_code == 0, r.stderr # Now rename # * doesn't include the `survey_reference` column # * has the columns in a different order # * has a new column with Db_GPKG.create_engine( data / "nz-waca-adjustments.gpkg").connect() as conn: conn.execute(""" ALTER TABLE "nz_waca_adjustments" RENAME COLUMN "survey_reference" TO "renamed_survey_reference"; """) r = cli_runner.invoke([ "import", "--replace-existing", data / "nz-waca-adjustments.gpkg", "nz_waca_adjustments:mytable", ]) assert r.exit_code == 0, r.stderr r = cli_runner.invoke(["show", "-o", "json"]) assert r.exit_code == 0, r.stderr diff = json.loads(r.stdout)["kart.diff/v1+hexwkb"]["mytable"] # The schema changed, but the features didn't. assert diff["meta"]["schema.json"] assert not diff.get("feature") repo = KartRepo(repo_path) head_rs = repo.structure("HEAD") old_rs = repo.structure("HEAD^") assert head_rs.tree != old_rs.tree new_feature_tree = head_rs.tree / "mytable/.table-dataset/feature" old_feature_tree = old_rs.tree / "mytable/.table-dataset/feature" assert new_feature_tree == old_feature_tree
def test_resolve_with_version(data_archive, cli_runner): with data_archive("conflicts/polygons.tgz") as repo_path: repo = KartRepo(repo_path) r = cli_runner.invoke(["merge", "theirs_branch", "-o", "json"]) assert r.exit_code == 0, r.stderr assert json.loads(r.stdout)["kart.merge/v1"]["conflicts"] assert repo.state == KartRepoState.MERGING # Can't just complete the merge until we resolve the conflicts. r = cli_runner.invoke(["merge", "--continue"]) assert r.exit_code == INVALID_OPERATION conflict_ids = get_conflict_ids(cli_runner) resolutions = iter(["ancestor", "ours", "theirs", "delete"]) # Keep track of which order we resolve the conflicts - each conflict # resolved will have a primary key, and we resolve conflicts in # primary key order, but the primary keys are not contiguous. pk_order = [] # Each conflict also has an internal "conflict" key - just its index # in the original list of conflicts - these are contiguous, but # we don't necessarily resolve the conflicts in this order. ck_order = [] while conflict_ids: num_conflicts = len(conflict_ids) conflict_id = conflict_ids[0] pk = conflict_id.split(":", 2)[2] pk_order += [pk] r = cli_runner.invoke( ["resolve", conflict_id, f"--with={next(resolutions)}"]) assert r.exit_code == 0, r.stderr conflict_ids = get_conflict_ids(cli_runner) assert len(conflict_ids) == num_conflicts - 1 resolved_keys = MergeIndex.read_from_repo(repo).resolves.keys() ck_order += [k for k in resolved_keys if k not in ck_order] assert len(conflict_ids) == 0 merge_index = MergeIndex.read_from_repo(repo) assert len(merge_index.entries) == 237 assert len(merge_index.conflicts) == 4 assert len(merge_index.resolves) == 4 ck0, ck1, ck2, ck3 = ck_order # Conflict ck0 is resolved to ancestor, but the ancestor is None. assert merge_index.resolves[ck0] == [] assert merge_index.conflicts[ck0].ancestor is None assert merge_index.resolves[ck1] == [merge_index.conflicts[ck1].ours] assert merge_index.resolves[ck2] == [merge_index.conflicts[ck2].theirs] assert merge_index.resolves[ck3] == [] r = cli_runner.invoke(["merge", "--continue", "-m", "merge commit"]) assert r.exit_code == 0, r.stderr assert repo.head_commit.message == "merge commit" assert repo.state != KartRepoState.MERGING merged = repo.structure("HEAD") ours = repo.structure("ours_branch") theirs = repo.structure("theirs_branch") l = H.POLYGONS.LAYER pk0, pk1, pk2, pk3 = pk_order # Feature at pk0 was resolved to ancestor, which was None. assert get_json_feature(merged, l, pk0) is None assert get_json_feature(merged, l, pk1) == get_json_feature(ours, l, pk1) assert get_json_feature(merged, l, pk2) == get_json_feature(theirs, l, pk2) assert get_json_feature(merged, l, pk3) is None
def test_resolve_with_file(data_archive, cli_runner): with data_archive("conflicts/polygons.tgz") as repo_path: repo = KartRepo(repo_path) r = cli_runner.invoke( ["diff", "ancestor_branch..ours_branch", "-o", "geojson"]) assert r.exit_code == 0, r.stderr ours_geojson = json.loads(r.stdout)["features"][0] assert ours_geojson["id"] == "nz_waca_adjustments:feature:98001:I" r = cli_runner.invoke( ["diff", "ancestor_branch..theirs_branch", "-o", "geojson"]) assert r.exit_code == 0, r.stderr theirs_geojson = json.loads(r.stdout)["features"][0] assert theirs_geojson["id"] == "nz_waca_adjustments:feature:98001:I" r = cli_runner.invoke(["merge", "theirs_branch", "-o", "json"]) assert r.exit_code == 0, r.stderr assert json.loads(r.stdout)["kart.merge/v1"]["conflicts"] r = cli_runner.invoke(["conflicts", "-s", "-o", "json"]) assert r.exit_code == 0, r.stderr conflicts = json.loads(r.stdout)["kart.conflicts/v1"] add_add_conflict_pk = conflicts[H.POLYGONS.LAYER]["feature"][0] assert add_add_conflict_pk == 98001 # These IDs are irrelevant, but we change them to at least be unique. ours_geojson["id"] = "ours-feature" theirs_geojson["id"] = "theirs-feature" # Changing this ID means the two features no long conflict. theirs_geojson["properties"]["id"] = 98002 resolution = { "features": [ours_geojson, theirs_geojson], "type": "FeatureCollection", } (repo.workdir_path / "resolution.geojson").write_text( json.dumps(resolution)) r = cli_runner.invoke([ "resolve", f"{H.POLYGONS.LAYER}:feature:98001", "--with-file=resolution.geojson", ]) assert r.exit_code == 0, r.stderr merge_index = MergeIndex.read_from_repo(repo) assert len(merge_index.entries) == 237 assert len(merge_index.conflicts) == 4 assert len(merge_index.resolves) == 1 ck = next(iter(merge_index.resolves.keys())) assert len(merge_index.resolves[ck]) == 2 # Resolved with 2 features delete_remaining_conflicts(cli_runner) r = cli_runner.invoke(["merge", "--continue", "-m", "merge commit"]) assert r.exit_code == 0, r.stderr assert repo.head_commit.message == "merge commit" assert repo.state != KartRepoState.MERGING merged = repo.structure("HEAD") ours = repo.structure("ours_branch") theirs = repo.structure("theirs_branch") l = H.POLYGONS.LAYER # Both features are present in the merged repo, ours at 98001 and theirs at 98002. assert get_json_feature(merged, l, 98001) == get_json_feature(ours, l, 98001) # Theirs feature is slightly different - it has a new primary key. assert get_json_feature(merged, l, 98002) != get_json_feature( theirs, l, 98001) modified_theirs_json = get_json_feature(theirs, l, 98001) modified_theirs_json["id"] = 98002 assert get_json_feature(merged, l, 98002) == modified_theirs_json
def test_import_replace_existing_with_compatible_schema_changes( data_archive, tmp_path, cli_runner, chdir, ): with data_archive("gpkg-polygons") as data: repo_path = tmp_path / "emptydir" r = cli_runner.invoke(["init", repo_path]) assert r.exit_code == 0 with chdir(repo_path): r = cli_runner.invoke([ "import", data / "nz-waca-adjustments.gpkg", "nz_waca_adjustments:mytable", ]) assert r.exit_code == 0, r.stderr # Now replace with a table which # * doesn't include the `survey_reference` column # * has the columns in a different order # * has a new column with Db_GPKG.create_engine( data / "nz-waca-adjustments.gpkg").connect() as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS "nz_waca_adjustments_2" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "geom" MULTIPOLYGON, "date_adjusted" DATETIME, "adjusted_nodes" MEDIUMINT, "newcolumn" TEXT ); """) conn.execute(""" INSERT INTO nz_waca_adjustments_2 (id, geom, date_adjusted, adjusted_nodes, newcolumn) SELECT id, geom, date_adjusted, adjusted_nodes, NULL FROM nz_waca_adjustments; """) conn.execute("""DROP TABLE nz_waca_adjustments;""") conn.execute( """ALTER TABLE "nz_waca_adjustments_2" RENAME TO "nz_waca_adjustments";""" ) r = cli_runner.invoke([ "import", "--replace-existing", data / "nz-waca-adjustments.gpkg", "nz_waca_adjustments:mytable", ]) assert r.exit_code == 0, r.stderr r = cli_runner.invoke(["show", "-o", "json"]) assert r.exit_code == 0, r.stderr diff = json.loads(r.stdout)["kart.diff/v1+hexwkb"]["mytable"] # The schema changed, but the features didn't. assert diff["meta"]["schema.json"] assert not diff.get("feature") repo = KartRepo(repo_path) head_rs = repo.structure("HEAD") old_rs = repo.structure("HEAD^") assert head_rs.tree != old_rs.tree new_feature_tree = head_rs.tree / "mytable/.table-dataset/feature" old_feature_tree = old_rs.tree / "mytable/.table-dataset/feature" assert new_feature_tree == old_feature_tree
def test_edit_crs(data_archive, cli_runner, new_postgis_db_schema): with data_archive("points") as repo_path: repo = KartRepo(repo_path) H.clear_working_copy() with new_postgis_db_schema() as (postgres_url, postgres_schema): r = cli_runner.invoke(["create-workingcopy", postgres_url]) assert r.exit_code == 0, r.stderr wc = repo.working_copy assert wc.status() & WorkingCopyStatus.INITIALISED assert wc.status() & WorkingCopyStatus.HAS_DATA assert not wc.is_dirty() # The test is run inside a single transaction which we always roll back - # this is because we are editing the public.spatial_ref_sys table, which is shared by # everything in the postgis DB - we don't want these temporary changes to make other # tests fail, and we want to roll them immediately whether the test passes or fails. with pytest.raises(SucceedAndRollback): with wc.session() as sess: crs = sess.scalar( "SELECT srtext FROM public.spatial_ref_sys WHERE srid=4326" ) assert crs.startswith('GEOGCS["WGS 84",') assert crs.endswith('AUTHORITY["EPSG","4326"]]') # Make an unimportant, cosmetic change, while keeping the CRS basically EPSG:4326 crs = crs.replace('GEOGCS["WGS 84",', 'GEOGCS["WGS 1984",') sess.execute( """UPDATE public.spatial_ref_sys SET srtext=:srtext WHERE srid=4326;""", {"srtext": crs}, ) # kart diff hides differences between dataset CRS and WC CRS if they are both supposed to be EPSG:4326 # (or any other standard CRS). See POSTGIS_WC.md assert not wc.is_dirty() # Change the CRS authority to CUSTOM crs = crs.replace('AUTHORITY["EPSG","4326"]]', 'AUTHORITY["CUSTOM","4326"]]') sess.execute( """UPDATE public.spatial_ref_sys SET srtext=:srtext WHERE srid=4326;""", {"srtext": crs}, ) # Now kart diff should show the change, and it is possible to commit the change. assert wc.is_dirty() commit_id = repo.structure().commit_diff( wc.diff_to_tree(), "Modify CRS") wc.update_state_table_tree(commit_id.hex) assert not wc.is_dirty() r = cli_runner.invoke(["show"]) lines = r.stdout.splitlines() assert "--- nz_pa_points_topo_150k:meta:crs/EPSG:4326.wkt" in lines assert ( "+++ nz_pa_points_topo_150k:meta:crs/CUSTOM:4326.wkt" in lines) raise SucceedAndRollback()