diff --git a/cz.bugsy.roster.json b/cz.bugsy.roster.json
index 8ac31e4..ae766a5 100644
--- a/cz.bugsy.roster.json
+++ b/cz.bugsy.roster.json
@@ -35,7 +35,7 @@
"sources" : [
{
"type" : "git",
- "url" : "file:///home/pavelb/GnomeBuilderProjects/Roster"
+ "url" : "file:///home/pavelb/Projects/GnomeBuilderProjects/Roster"
}
],
"config-opts" : [
diff --git a/src/environments_dialog.py b/src/environments_dialog.py
index e8c6baf..4616013 100644
--- a/src/environments_dialog.py
+++ b/src/environments_dialog.py
@@ -18,6 +18,7 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
+import re
from gi.repository import Adw, Gtk, GObject
from .widgets.variable_row import VariableRow
from .widgets.environment_row import EnvironmentRow
@@ -65,6 +66,8 @@ class EnvironmentsDialog(Adw.Dialog):
self._on_environment_edit)
self.header_row.connect('environment-delete-requested',
self._on_environment_delete)
+ self.header_row.connect('environment-copy-requested',
+ self._on_environment_copy)
self.table_container.append(self.header_row)
# Add separator
@@ -249,6 +252,30 @@ class EnvironmentsDialog(Adw.Dialog):
dialog.connect("response", on_response)
dialog.present(self)
+ def _on_environment_copy(self, widget, environment):
+ """Copy an environment with a unique name."""
+ existing_names = {env.name for env in self.project.environments}
+ new_name = self._unique_env_name(environment.name, existing_names)
+
+ def on_copy_complete(new_env):
+ self._reload_project()
+ self._populate_table()
+ self.emit('environments-updated')
+
+ self.project_manager.copy_environment(self.project.id, environment.id, new_name, on_copy_complete)
+
+ @staticmethod
+ def _unique_env_name(base_name, existing_names):
+ """Generate a unique environment name by appending (1), (2), etc."""
+ match = re.match(r'^(.*?) \((\d+)\)$', base_name)
+ base = match.group(1) if match else base_name
+ counter = 1
+ while True:
+ candidate = f"{base} ({counter})"
+ if candidate not in existing_names:
+ return candidate
+ counter += 1
+
def _on_environment_delete(self, widget, environment):
"""Delete environment with confirmation."""
# Prevent deletion of last environment
diff --git a/src/project_manager.py b/src/project_manager.py
index 27edca7..23c954c 100644
--- a/src/project_manager.py
+++ b/src/project_manager.py
@@ -229,6 +229,73 @@ class ProjectManager:
self.save_projects(projects)
return environment
+ def copy_environment(self, project_id: str, source_env_id: str, new_name: str,
+ callback: Optional[Callable] = None):
+ """Copy an environment including all variables (sensitive ones via keyring)."""
+ projects = self.load_projects()
+ secret_manager = get_secret_manager()
+
+ for p in projects:
+ if p.id == project_id:
+ source_env = next((e for e in p.environments if e.id == source_env_id), None)
+ if source_env is None:
+ if callback:
+ callback(None)
+ return
+
+ now = datetime.now(timezone.utc).isoformat()
+ new_env_id = str(uuid.uuid4())
+ new_env = Environment(
+ id=new_env_id,
+ name=new_name,
+ variables=dict(source_env.variables),
+ created_at=now
+ )
+ p.environments.append(new_env)
+ self.save_projects(projects)
+
+ sensitive_vars = [v for v in p.sensitive_variables if v in source_env.variables]
+ if not sensitive_vars:
+ if callback:
+ callback(new_env)
+ return
+
+ pending_count = [len(sensitive_vars)]
+
+ def make_copy_handler(var_name, project_name, env_name):
+ def on_store_done(success=True):
+ pending_count[0] -= 1
+ if pending_count[0] == 0 and callback:
+ callback(new_env)
+
+ def on_retrieve(value):
+ if value:
+ secret_manager.store_secret(
+ project_id=project_id,
+ environment_id=new_env_id,
+ variable_name=var_name,
+ value=value,
+ project_name=project_name,
+ environment_name=env_name,
+ callback=lambda success: on_store_done(success)
+ )
+ else:
+ on_store_done()
+
+ return on_retrieve
+
+ for var_name in sensitive_vars:
+ secret_manager.retrieve_secret(
+ project_id=project_id,
+ environment_id=source_env_id,
+ variable_name=var_name,
+ callback=make_copy_handler(var_name, p.name, new_name)
+ )
+ return
+
+ if callback:
+ callback(None)
+
def update_environment(self, project_id: str, env_id: str, name: str = None, variables: dict = None):
"""Update an environment's name and/or variables."""
projects = self.load_projects()
diff --git a/src/widgets/environment-column-header.ui b/src/widgets/environment-column-header.ui
index 714b4de..732e9a0 100644
--- a/src/widgets/environment-column-header.ui
+++ b/src/widgets/environment-column-header.ui
@@ -23,6 +23,18 @@
center
4
+
+
+
+