Coverage for gsb/backup.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-08 20:16 +0000

1"""Functionality for creating backups""" 

2 

3import datetime as dt 

4import logging 

5from pathlib import Path 

6 

7from . import _git 

8from .logging import IMPORTANT 

9from .manifest import MANIFEST_NAME, Manifest 

10 

11REQUIRED_FILES: tuple[Path, ...] = (Path(".gitignore"), Path(MANIFEST_NAME)) 

12 

13LOGGER = logging.getLogger(__name__) 

14 

15 

16def generate_tag_name() -> str: 

17 """Generate a new calver-ish tag name 

18 

19 Returns 

20 ------- 

21 str 

22 A tag name that should hopefully be distinctly GSB and distinct 

23 from any tags a user would manually create 

24 

25 Notes 

26 ----- 

27 When unit testing, this method will need to be monkeypatched to provide 

28 even greater granularity... unless you want to use `time.sleep(1)` between 

29 each tag :O 

30 """ 

31 return dt.datetime.now().strftime("gsb%Y.%m.%d+%H%M%S") 

32 

33 

34def create_backup( 

35 repo_root: Path, 

36 tag_message: str | None = None, 

37 commit_message: str | None = None, 

38 parent: str | None = None, 

39 tag_name: str | None = None, 

40) -> str: 

41 """Create a new backup 

42 

43 Parameters 

44 ---------- 

45 repo_root : Path 

46 The directory containing the GSB-managed repo 

47 tag_message : str, optional 

48 By default, this method just creates an "untagged" backup with a default 

49 commit message. To tag this backup, provide a description of the backup 

50 to use for both the commit message and the tag annotation. 

51 commit_message : str, optional 

52 By default, the commit message will match the `tag_message`, if one 

53 is provided. Provide a value to this argument to explicitly set the 

54 commit message. If neither a `tag` nor a `commit_message` is provided, 

55 the default value will be "GSB-managed commit" 

56 parent: str, optional 

57 By default, this new backup will be created as an incremental commit 

58 off of the current head. To instead reset to a specific revision, 

59 pass in an ID for that revision to this argument. 

60 tag_name : str, optional 

61 By default, tag names are automatically generated. Use this argument to 

62 provide a custom tag name. This option is ignored when not creating 

63 a tag. 

64 

65 Returns 

66 ------- 

67 str 

68 An identifier for the backup in the form of either a commit hash or a 

69 tag name 

70 

71 Raises 

72 ------ 

73 OSError 

74 If the specified repo does not exist or is not a gsb-managed repo 

75 ValueError 

76 If there are no changes to commit and no tag message was provided, or 

77 if the specified "parent" could not be resolved. 

78 """ 

79 manifest = Manifest.of(repo_root) 

80 

81 if parent is not None: 

82 _git.reset(repo_root, parent, hard=False) 

83 

84 _git.add(repo_root, manifest.patterns) 

85 _git.force_add(repo_root, REQUIRED_FILES) 

86 try: 

87 identifier = _git.commit( 

88 repo_root, commit_message or tag_message or "GSB-managed commit" 

89 ).hash 

90 LOGGER.info("Changes committed with hash %s", identifier[:8]) 

91 LOGGER.debug("Full hash: %s", identifier) 

92 except ValueError: 

93 if not tag_message: 

94 raise 

95 LOGGER.info("Nothing new to commit--all files are up-to-date.") 

96 if tag_message: 

97 head = _git.show(repo_root, "HEAD") 

98 for tag in _git.get_tags(repo_root, annotated_only=True): 

99 if tag.target == head: 

100 raise ValueError(f"The current HEAD is already tagged as {tag.name}") 

101 identifier = _git.tag( 

102 repo_root, tag_name or generate_tag_name(), tag_message 

103 ).name 

104 LOGGER.log(IMPORTANT, "Created new tagged backup: %s", identifier) 

105 return identifier