Skip to content

Commit 3506890

Browse files
authored
Add project renaming + CLI to reset changes (#192)
1 parent f555e71 commit 3506890

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ Commands:
7575
pull Fetch changes from Mergin Maps repository
7676
push Upload local changes into Mergin Maps repository
7777
remove Remove project from server.
78+
rename Rename project in Mergin Maps repository.
79+
reset Reset local changes in project.
7880
share Fetch permissions to project
7981
share-add Add permissions to [users] to project
8082
share-remove Remove [users] permissions from project

mergin/cli.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,5 +602,57 @@ def resolve_unfinished_pull(ctx):
602602
_print_unhandled_exception()
603603

604604

605+
@cli.command()
606+
@click.argument("project_path")
607+
@click.argument("new_project_name")
608+
@click.pass_context
609+
def rename(ctx, project_path: str, new_project_name: str):
610+
"""Rename project in Mergin Maps repository."""
611+
mc = ctx.obj["client"]
612+
if mc is None:
613+
return
614+
615+
if "/" not in project_path:
616+
click.secho(f"Specify `project_path` as full name (<namespace>/<name>) instead.", fg="red")
617+
return
618+
619+
if "/" in new_project_name:
620+
old_workspace, old_project_name = project_path.split("/")
621+
new_workspace, new_project_name = new_project_name.split("/")
622+
623+
if old_workspace != new_workspace:
624+
click.secho(
625+
"`new_project_name` should not contain namespace, project can only be rename within their namespace.\nTo move project to another workspace use web dashboard.",
626+
fg="red",
627+
)
628+
return
629+
630+
try:
631+
mc.rename_project(project_path, new_project_name)
632+
click.echo("Project renamed")
633+
except ClientError as e:
634+
click.secho("Error: " + str(e), fg="red")
635+
except Exception as e:
636+
_print_unhandled_exception()
637+
638+
639+
@cli.command()
640+
@click.pass_context
641+
def reset(ctx):
642+
"""Reset local changes in project."""
643+
directory = os.getcwd()
644+
mc: MerginClient = ctx.obj["client"]
645+
if mc is None:
646+
return
647+
try:
648+
mc.reset_local_changes(directory)
649+
except InvalidProject as e:
650+
click.secho("Invalid project directory ({})".format(str(e)), fg="red")
651+
except ClientError as e:
652+
click.secho("Error: " + str(e), fg="red")
653+
except Exception as e:
654+
_print_unhandled_exception()
655+
656+
605657
if __name__ == "__main__":
606658
cli()

mergin/client.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,36 @@ def has_writing_permissions(self, project_path):
11311131
info = self.project_info(project_path)
11321132
return info["permissions"]["upload"]
11331133

1134+
def rename_project(self, project_path: str, new_project_name: str) -> None:
1135+
"""
1136+
Rename project on server.
1137+
1138+
:param project_path: Project's full name (<namespace>/<name>)
1139+
:type project_path: String
1140+
:param new_project_name: Project's new name (<name>)
1141+
:type new_project_name: String
1142+
"""
1143+
# TODO: this version check should be replaced by checking against the list
1144+
# of endpoints that server publishes in /config (once implemented)
1145+
if not is_version_acceptable(self.server_version(), "2023.5.4"):
1146+
raise NotImplementedError("This needs server at version 2023.5.4 or later")
1147+
1148+
if "/" in new_project_name:
1149+
raise ClientError(
1150+
"Project's new name should be without workspace specification (<name>). Project can only be renamed within its current workspace."
1151+
)
1152+
1153+
project_info = self.project_info(project_path)
1154+
project_id = project_info["id"]
1155+
path = "/v2/projects/" + project_id
1156+
url = urllib.parse.urljoin(self.url, urllib.parse.quote(path))
1157+
json_headers = {"Content-Type": "application/json"}
1158+
data = {"name": new_project_name}
1159+
request = urllib.request.Request(
1160+
url, data=json.dumps(data).encode("utf-8"), headers=json_headers, method="PATCH"
1161+
)
1162+
self._do_request(request)
1163+
11341164
def reset_local_changes(self, directory: str, files_to_reset: typing.List[str] = None) -> None:
11351165
"""
11361166
Reset local changes to either all files or only listed files.

mergin/test/test_client.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,56 @@ def test_project_metadata(mc):
21692169
assert mp.version() == "v0"
21702170

21712171

2172+
def test_project_rename(mc: MerginClient):
2173+
"""Check project can be renamed"""
2174+
2175+
test_project = "test_project_rename"
2176+
test_project_renamed = "test_project_renamed"
2177+
project = API_USER + "/" + test_project
2178+
project_renamed = API_USER + "/" + test_project_renamed
2179+
2180+
project_dir = os.path.join(TMP_DIR, test_project) # primary project dir
2181+
2182+
cleanup(mc, project, [project_dir])
2183+
cleanup(mc, project_renamed, [])
2184+
2185+
shutil.copytree(TEST_DATA_DIR, project_dir)
2186+
mc.create_project_and_push(project, project_dir)
2187+
2188+
# renamed project does not exist
2189+
with pytest.raises(ClientError, match="The requested URL was not found on the server"):
2190+
info = mc.project_info(project_renamed)
2191+
2192+
# rename
2193+
mc.rename_project(project, test_project_renamed)
2194+
2195+
# validate project info
2196+
project_info = mc.project_info(project_renamed)
2197+
assert project_info["version"] == "v1"
2198+
assert project_info["name"] == test_project_renamed
2199+
assert project_info["namespace"] == API_USER
2200+
with pytest.raises(ClientError, match="The requested URL was not found on the server"):
2201+
mc.project_info(project)
2202+
2203+
# recreate project
2204+
cleanup(mc, project, [project_dir])
2205+
shutil.copytree(TEST_DATA_DIR, project_dir)
2206+
mc.create_project_and_push(project, project_dir)
2207+
2208+
# rename to existing name - created previously
2209+
mc.project_info(project_renamed)
2210+
with pytest.raises(ClientError, match="Name already exist within workspace"):
2211+
mc.rename_project(project, test_project_renamed)
2212+
2213+
# cannot rename project that does not exist
2214+
with pytest.raises(ClientError, match="The requested URL was not found on the server."):
2215+
mc.rename_project(API_USER + "/" + "non_existing_project", "new_project")
2216+
2217+
# cannot rename with full project name
2218+
with pytest.raises(ClientError, match="Project's new name should be without workspace specification"):
2219+
mc.rename_project(project, "workspace" + "/" + test_project_renamed)
2220+
2221+
21722222
def test_download_files(mc: MerginClient):
21732223
"""Test downloading files at specified versions."""
21742224
test_project = "test_download_files"

0 commit comments

Comments
 (0)