add revert to tags function
This commit is contained in:
parent
97ac687f1d
commit
c84348b263
@ -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
|
||||
|
||||
@ -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 ---
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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 ---
|
||||
|
||||
@ -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 ---
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user