util.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. """Module containing index utilities"""
  2. from functools import wraps
  3. import os
  4. import struct
  5. import tempfile
  6. from git.compat import is_win
  7. import os.path as osp
  8. # typing ----------------------------------------------------------------------
  9. from typing import Any, Callable, TYPE_CHECKING
  10. from git.types import PathLike, _T
  11. if TYPE_CHECKING:
  12. from git.index import IndexFile
  13. # ---------------------------------------------------------------------------------
  14. __all__ = ("TemporaryFileSwap", "post_clear_cache", "default_index", "git_working_dir")
  15. # { Aliases
  16. pack = struct.pack
  17. unpack = struct.unpack
  18. # } END aliases
  19. class TemporaryFileSwap(object):
  20. """Utility class moving a file to a temporary location within the same directory
  21. and moving it back on to where on object deletion."""
  22. __slots__ = ("file_path", "tmp_file_path")
  23. def __init__(self, file_path: PathLike) -> None:
  24. self.file_path = file_path
  25. self.tmp_file_path = str(self.file_path) + tempfile.mktemp("", "", "")
  26. # it may be that the source does not exist
  27. try:
  28. os.rename(self.file_path, self.tmp_file_path)
  29. except OSError:
  30. pass
  31. def __del__(self) -> None:
  32. if osp.isfile(self.tmp_file_path):
  33. if is_win and osp.exists(self.file_path):
  34. os.remove(self.file_path)
  35. os.rename(self.tmp_file_path, self.file_path)
  36. # END temp file exists
  37. # { Decorators
  38. def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
  39. """Decorator for functions that alter the index using the git command. This would
  40. invalidate our possibly existing entries dictionary which is why it must be
  41. deleted to allow it to be lazily reread later.
  42. :note:
  43. This decorator will not be required once all functions are implemented
  44. natively which in fact is possible, but probably not feasible performance wise.
  45. """
  46. @wraps(func)
  47. def post_clear_cache_if_not_raised(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
  48. rval = func(self, *args, **kwargs)
  49. self._delete_entries_cache()
  50. return rval
  51. # END wrapper method
  52. return post_clear_cache_if_not_raised
  53. def default_index(func: Callable[..., _T]) -> Callable[..., _T]:
  54. """Decorator assuring the wrapped method may only run if we are the default
  55. repository index. This is as we rely on git commands that operate
  56. on that index only."""
  57. @wraps(func)
  58. def check_default_index(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
  59. if self._file_path != self._index_path():
  60. raise AssertionError(
  61. "Cannot call %r on indices that do not represent the default git index" % func.__name__
  62. )
  63. return func(self, *args, **kwargs)
  64. # END wrapper method
  65. return check_default_index
  66. def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
  67. """Decorator which changes the current working dir to the one of the git
  68. repository in order to assure relative paths are handled correctly"""
  69. @wraps(func)
  70. def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
  71. cur_wd = os.getcwd()
  72. os.chdir(str(self.repo.working_tree_dir))
  73. try:
  74. return func(self, *args, **kwargs)
  75. finally:
  76. os.chdir(cur_wd)
  77. # END handle working dir
  78. # END wrapper
  79. return set_git_working_dir
  80. # } END decorators