add revert to tags function

This commit is contained in:
VALLONGOL 2025-06-16 16:05:38 +02:00
parent 97ac687f1d
commit c84348b263
6 changed files with 290 additions and 0 deletions

View File

@ -188,6 +188,7 @@ class GitSvnSyncApp:
refresh_tags_cb=self.refresh_tag_list,
create_tag_cb=self.create_tag,
checkout_tag_cb=self.checkout_tag,
revert_to_tag_cb=self.revert_to_tag,
# Branches Callbacks (Local)
refresh_branches_cb=self.refresh_branch_list,
create_branch_cb=self.create_branch,
@ -4114,6 +4115,73 @@ class GitSvnSyncApp:
}
)
def revert_to_tag(self):
"""
Handles the destructive 'Revert to Tag' action.
Confirms with user, then starts async hard reset operation.
"""
func_name: str = "revert_to_tag"
log_handler.log_info(
f"--- Action Triggered: Revert to Tag ---", func_name=func_name
)
# Assicurati che il frame principale esista
if not hasattr(self, "main_frame") or not self.main_frame.winfo_exists():
return
# Valida il percorso del repository
svn_path: Optional[str] = self._get_and_validate_svn_path("Revert to Tag")
if not svn_path or not self._is_repo_ready(svn_path):
log_handler.log_warning(
"Revert to Tag failed: Repo not ready.", func_name=func_name
)
self.main_frame.show_error("Action Failed", "Repository is not ready.")
self.main_frame.update_status_bar("Revert failed: Repo not ready.")
return
# Ottieni il tag selezionato dalla GUI
tag_name: Optional[str] = self.main_frame.get_selected_tag()
if not tag_name:
self.main_frame.show_error(
"Selection Error", "No tag selected from the list."
)
self.main_frame.update_status_bar("Revert failed: No tag selected.")
return
# --- Messaggio di Avviso Critico ---
warning_message = (
f"WARNING: Destructive Operation\n\n"
f"You are about to reset the repository to the state of tag '{tag_name}'.\n\n"
"This operation will PERMANENTLY DELETE:\n"
" • All uncommitted changes.\n"
" • All new untracked files.\n"
" • All commits made on this branch after the tag.\n\n"
"This action cannot be undone.\n\n"
"Are you absolutely sure you want to proceed?"
)
if not self.main_frame.ask_yes_no("Confirm Destructive Revert", warning_message):
# L'utente ha annullato
log_handler.log_info("Revert to tag cancelled by user.", func_name=func_name)
self.main_frame.update_status_bar("Revert cancelled.")
return
# Prepara gli argomenti e avvia l'operazione asincrona
log_handler.log_warning(
f"User confirmed destructive revert to tag '{tag_name}'. Starting worker...",
func_name=func_name
)
args: tuple = (self.action_handler, svn_path, tag_name)
self._start_async_operation(
worker_func=async_workers.run_revert_to_tag_async,
args_tuple=args,
context_dict={
"context": "revert_to_tag",
"status_msg": f"Reverting to tag '{tag_name}'",
"tag_name": tag_name, # Passa il tag nel contesto per il result handler
},
)
# --- Application Entry Point ---
# Questa parte verrà spostata in __main__.py

View File

@ -137,6 +137,7 @@ class AsyncResultHandler:
"checkout_tracking_branch": self._handle_checkout_tracking_branch_result,
"get_commit_details": self._handle_get_commit_details_result,
"update_wiki": self._handle_generic_result,
"revert_to_tag": self._handle_revert_to_tag_result,
}
# Get the handler method from the map
@ -1471,5 +1472,46 @@ class AsyncResultHandler:
self.app.master.after(delay_ms + 50, _reenable_widgets_final)
def _handle_revert_to_tag_result(
self, result_data: Dict[str, Any], context: Dict[str, Any]
) -> Tuple[bool, bool]:
"""
Handles the result of the 'revert to tag' operation.
Shows success/error message and triggers a full GUI refresh on success.
"""
func_name: str = "_handle_revert_to_tag_result"
status: Optional[str] = result_data.get("status")
message: Optional[str] = result_data.get("message")
tag_name_ctx: Optional[str] = context.get("tag_name")
trigger_refreshes: bool = False
sync_refresh: bool = False
if status == "success":
log_handler.log_info(
f"Successfully reverted repository to tag '{tag_name_ctx}'. Triggering full refresh.",
func_name=func_name,
)
# Mostra un messaggio di informazione all'utente
if hasattr(self.main_frame, "show_info"):
self.main_frame.show_info("Operation Successful", message)
# Segnala la necessità di un refresh completo della GUI
trigger_refreshes = True
sync_refresh = True
elif status == "error":
log_handler.log_error(
f"Revert to tag '{tag_name_ctx}' failed: {message}", func_name=func_name
)
# Mostra un messaggio di errore all'utente
if hasattr(self.main_frame, "show_error"):
self.main_frame.show_error("Revert to Tag Error", f"Failed:\n{message}")
# Non è necessario chiamare _reenable_widgets_after_modal() qui,
# perché o il refresh lo farà, o il gestore generico lo farà se non ci sono refresh.
return trigger_refreshes, sync_refresh
# --- End of AsyncResultHandler Class ---

View File

@ -2191,3 +2191,55 @@ def run_update_wiki_async(
except Exception as qe:
log_handler.log_error(f"[Worker] Failed to put result in queue for {func_name}: {qe}", func_name=func_name)
log_handler.log_debug("[Worker] Finished: Update Gitea Wiki", func_name=func_name)
def run_revert_to_tag_async(
action_handler: ActionHandler,
repo_path: str,
tag_name: str,
results_queue: queue.Queue[Dict[str, Any]],
) -> None:
"""Worker to perform a hard reset to a tag asynchronously."""
func_name: str = "run_revert_to_tag_async"
log_handler.log_debug(
f"[Worker] Started: Revert to Tag '{tag_name}' in '{repo_path}'",
func_name=func_name,
)
result_payload: Dict[str, Any] = {
"status": "error",
"result": False,
"message": f"Failed to revert to tag '{tag_name}'.",
"exception": None,
}
try:
# Chiama il metodo dell'ActionHandler
success: bool = action_handler.execute_revert_to_tag(repo_path, tag_name)
# Prepara il risultato di successo
result_payload["status"] = "success"
result_payload["result"] = success
result_payload["message"] = f"Repository successfully reverted to tag '{tag_name}'."
log_handler.log_info(f"[Worker] {result_payload['message']}", func_name=func_name)
except (GitCommandError, ValueError, Exception) as e:
# Cattura qualsiasi eccezione dall'ActionHandler
log_handler.log_exception(
f"[Worker] EXCEPTION reverting to tag: {e}", func_name=func_name
)
result_payload["exception"] = e
result_payload["message"] = f"Error reverting to tag '{tag_name}': {e}"
# Se l'errore è specifico, potremmo volerlo mostrare in modo diverso
if "not found" in str(e).lower():
result_payload["message"] = f"Error: Tag '{tag_name}' not found."
finally:
# Metti sempre il risultato nella coda, sia in caso di successo che di errore
try:
results_queue.put(result_payload)
except Exception as qe:
log_handler.log_error(
f"[Worker] Failed to put result in queue for {func_name}: {qe}",
func_name=func_name,
)
log_handler.log_debug(
f"[Worker] Finished: Revert to Tag '{tag_name}'", func_name=func_name
)

View File

@ -1983,5 +1983,61 @@ class GitCommands:
)
return -1, []
def git_reset_hard(self, working_directory: str, reference: str) -> None:
"""
Performs a hard reset to a given reference and cleans the working directory.
WARNING: This is a destructive operation.
Args:
working_directory (str): The path to the Git repository.
reference (str): The tag, branch, or commit hash to reset to.
Raises:
ValueError: If the reference is empty.
GitCommandError: If any of the Git commands fail.
"""
func_name: str = "git_reset_hard"
if not reference or reference.isspace():
raise ValueError("Reference for reset cannot be empty.")
log_handler.log_warning(
f"Performing DESTRUCTIVE operation: git reset --hard {reference}",
func_name=func_name,
)
# 1. Esegui git reset --hard
reset_cmd: List[str] = ["git", "reset", "--hard", reference]
try:
self.log_and_execute(
reset_cmd, working_directory, check=True, log_output_level=logging.INFO
)
log_handler.log_info(
f"Hard reset to '{reference}' completed successfully.", func_name=func_name
)
except GitCommandError as e:
log_handler.log_error(
f"git reset --hard to '{reference}' FAILED.", func_name=func_name
)
# Rilancia l'eccezione per fermare l'operazione
raise e
log_handler.log_warning(
"Performing DESTRUCTIVE operation: git clean -fdx", func_name=func_name
)
# 2. Esegui git clean -fdx per rimuovere file non tracciati
clean_cmd: List[str] = ["git", "clean", "-fdx"]
try:
self.log_and_execute(
clean_cmd, working_directory, check=True, log_output_level=logging.INFO
)
log_handler.log_info(
"Clean of untracked files completed successfully.", func_name=func_name
)
except GitCommandError as e:
log_handler.log_error("git clean -fdx FAILED.", func_name=func_name)
# Rilancia l'eccezione
raise e
# --- END OF FILE gitsync_tool/commands/git_commands.py ---

View File

@ -1640,5 +1640,60 @@ class ActionHandler:
return result_info
def execute_revert_to_tag(self, repo_path: str, tag_name: str) -> bool:
"""
Executes a hard reset to the specified tag and cleans the working directory.
This is a wrapper for a destructive operation.
Args:
repo_path (str): The path to the repository.
tag_name (str): The tag to revert to.
Returns:
bool: True if the operation was successful.
Raises:
ValueError: If inputs are invalid.
GitCommandError: If the underlying git commands fail.
"""
func_name: str = "execute_revert_to_tag"
log_handler.log_warning(
f"Executing destructive revert to tag '{tag_name}' in repo: {repo_path}",
func_name=func_name,
)
# --- Validazione Input ---
if not repo_path or not os.path.isdir(repo_path):
raise ValueError(f"Invalid repository path provided: '{repo_path}'")
if not tag_name or tag_name.isspace():
raise ValueError("Tag name for revert cannot be empty.")
if not os.path.exists(os.path.join(repo_path, ".git")):
raise ValueError(f"Directory '{repo_path}' is not a valid Git repository.")
try:
# Chiama il metodo di basso livello in GitCommands
self.git_commands.git_reset_hard(repo_path, tag_name)
log_handler.log_info(
f"Successfully reverted repository to tag '{tag_name}'.",
func_name=func_name,
)
return True # L'operazione è andata a buon fine
except (GitCommandError, ValueError) as e:
# Logga e rilancia l'eccezione per essere gestita dal worker
log_handler.log_error(
f"Failed to revert to tag '{tag_name}': {e}", func_name=func_name
)
raise e
except Exception as e:
# Cattura altri errori imprevisti
log_handler.log_exception(
f"An unexpected error occurred during revert to tag '{tag_name}': {e}",
func_name=func_name,
)
# Rilancia come un'eccezione generica o GitCommandError per coerenza
raise Exception(f"Unexpected revert error: {e}") from e
# --- END OF FILE gitsync_tool/core/action_handler.py ---

View File

@ -74,6 +74,7 @@ class MainFrame(ttk.Frame):
refresh_tags_cb: Callable[[], None],
create_tag_cb: Callable[[], None],
checkout_tag_cb: Callable[[], None],
revert_to_tag_cb: Callable[[], None],
refresh_history_cb: Callable[[], None],
refresh_branches_cb: Callable[[], None], # Callback unico per refresh locali
checkout_branch_cb: Callable[
@ -123,6 +124,7 @@ class MainFrame(ttk.Frame):
self.refresh_tags_callback = refresh_tags_cb
self.create_tag_callback = create_tag_cb
self.checkout_tag_callback = checkout_tag_cb
self.revert_to_tag_callback = revert_to_tag_cb
self.refresh_history_callback = refresh_history_cb
self.refresh_branches_callback = refresh_branches_cb
self.checkout_branch_callback = checkout_branch_cb
@ -756,6 +758,20 @@ class MainFrame(ttk.Frame):
self.checkout_tag_button,
"Switch the working directory to the state of the selected tag (Detached HEAD).",
)
self.revert_to_tag_button = ttk.Button(
button_frame,
text="Revert to this Tag",
width=bw,
command=self.revert_to_tag_callback, # Nuovo callback
state=tk.DISABLED,
)
self.revert_to_tag_button.pack(side=tk.TOP, fill=tk.X, pady=5)
self.create_tooltip(
self.revert_to_tag_button,
"DESTRUCTIVE: Resets the current branch to this tag.\nAll later commits and uncommitted changes will be lost.",
)
return frame
def _create_branch_tab(self):
@ -1857,6 +1873,7 @@ class MainFrame(ttk.Frame):
"refresh_tags_button",
"create_tag_button",
"checkout_tag_button",
"revert_to_tag_button",
"refresh_branches_button",
"create_branch_button",
"checkout_branch_button",