@@ -54,7 +54,7 @@ Features:
5454This tool is intended for use within Git repositories.
5555"""
5656
57- __version__ = "0.3.3 "
57+ __version__ = "0.3.4 "
5858
5959import argparse
6060import json
@@ -808,6 +808,63 @@ def clutil_get_current_branch() -> Optional[str]:
808808 return None
809809
810810
811+ @contextmanager
812+ def clutil_safe_stash_context (git_root : Path ,
813+ files_for_git : list [str ],
814+ untracked_files_for_git : list [str ]):
815+ """
816+ Context manager to handle directory changes during stash operations.
817+ Recalculates file paths relative to git root.
818+
819+ Args:
820+ git_root: Path to git repository root
821+ files_for_git: Original file paths relative to current directory
822+ untracked_files_for_git: Original untracked file paths relative to current directory
823+
824+ Yields:
825+ tuple[list[str], list[str]]: (files_for_git, untracked_files_for_git) relative to git root
826+ """
827+ original_cwd = Path .cwd ()
828+
829+ def convert_to_git_root_relative (file_paths : list [str ]) -> list [str ]:
830+ """Convert file paths to be relative to git root."""
831+ result = []
832+ for file_path in file_paths :
833+ abs_path = (original_cwd / file_path ).resolve ()
834+ try :
835+ git_root_relative = abs_path .relative_to (git_root )
836+ result .append (git_root_relative .as_posix ())
837+ except ValueError :
838+ # Path is outside git root - should not happen with validation
839+ result .append (file_path )
840+ return result
841+
842+ # Convert both file lists
843+ safe_files = convert_to_git_root_relative (files_for_git )
844+ safe_untracked_files = convert_to_git_root_relative (untracked_files_for_git )
845+
846+ # Change to git root
847+ os .chdir (git_root )
848+
849+ try :
850+ yield safe_files , safe_untracked_files
851+ finally :
852+ # Try to restore original directory
853+ if original_cwd .exists ():
854+ os .chdir (original_cwd )
855+ else :
856+ # Directory was deleted - warn user and stay in git root
857+ try :
858+ rel_path = os .path .relpath (original_cwd , git_root )
859+ print (f"Note: Directory '{ rel_path } ' was removed by git stash." )
860+ print ("Your shell is still pointing to the deleted directory." )
861+ print ("Please run: cd .." )
862+ except (ValueError , OSError ):
863+ print ("Note: Your working directory was removed by git stash." )
864+ print ("Your shell is still pointing to the deleted directory." )
865+ print ("Please run: cd .." )
866+
867+
811868def clutil_get_stash_source_branch (stash_data : dict ) -> Optional [str ]:
812869 """
813870 Extract the branch name from stash metadata if stored.
@@ -928,6 +985,7 @@ def clutil_rollback_stash(stash_ref: str, changelist_name: str) -> bool:
928985 f"'{ changelist_name } '" )
929986 return False
930987
988+
931989def clutil_stash_all_changelists (changelists : dict [str , list [str ]], quiet : bool = False ) -> None :
932990 """
933991 Helper function to stash all active changelists.
@@ -2664,10 +2722,12 @@ def cl_stash(args: argparse.Namespace, quiet: bool = False) -> None:
26642722 stashable_files , file_categories , git_root , quiet = quiet
26652723 )
26662724
2667- # Execute git stash
2725+ # Execute git stash with safe directory handling
26682726 try :
2669- stash_ref = clutil_execute_git_stash (name , files_for_git ,
2670- untracked_files_for_git )
2727+ with clutil_safe_stash_context (
2728+ git_root , files_for_git , untracked_files_for_git ) as (safe_files ,
2729+ safe_untracked_files ):
2730+ stash_ref = clutil_execute_git_stash (name , safe_files , safe_untracked_files )
26712731 except (subprocess .CalledProcessError , ValueError ):
26722732 return
26732733
@@ -2689,6 +2749,7 @@ def cl_stash(args: argparse.Namespace, quiet: bool = False) -> None:
26892749 clutil_handle_stash_failure (error , name , stash_ref ,
26902750 original_changelists , stashes )
26912751
2752+
26922753def cl_unstash (args : argparse .Namespace , quiet : bool = False ) -> None :
26932754 """
26942755 Restore a stashed changelist to the working directory.
@@ -2727,7 +2788,8 @@ def cl_unstash(args: argparse.Namespace, quiet: bool = False) -> None:
27272788
27282789 # Check for conflicts (suppress verbose output in quiet mode)
27292790 if not clutil_check_and_report_conflicts (files , git_root , base_name ,
2730- getattr (args , 'force' , False ), quiet = quiet ):
2791+ getattr (args , 'force' , False ),
2792+ quiet = quiet ):
27312793 return
27322794
27332795 # Verify stash still exists and update reference
@@ -2839,7 +2901,8 @@ def cl_branch(args: argparse.Namespace) -> None:
28392901
28402902 except Exception as error :
28412903 print (f"Error unstashing changelist: { error } " )
2842- print (f"Branch '{ branch_name } ' was created but changelist restore failed." )
2904+ print (f"Branch '{ branch_name } ' was created but"
2905+ "changelist restore failed." )
28432906 print (f"Try manually: git cl unstash { changelist_name } " )
28442907
28452908
0 commit comments