Coverage for gsb/backup.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.2.6, created at 2024-09-08 16:23 -0400

1"""Functionality for creating backups""" 

2import datetime as dt 

3import logging 

4from pathlib import Path 

5 

6from . import _git 

7from .logging import IMPORTANT 

8from .manifest import MANIFEST_NAME, Manifest 

9 

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

11 

12LOGGER = logging.getLogger(__name__) 

13 

14 

15def generate_tag_name() -> str: 

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

17 

18 Returns 

19 ------- 

20 str 

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

22 from any tags a user would manually create 

23 

24 Notes 

25 ----- 

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

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

28 each tag :O 

29 """ 

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

31 

32 

33def create_backup( 

34 repo_root: Path, 

35 tag_message: str | None = None, 

36 commit_message: str | None = None, 

37 parent: str | None = None, 

38 tag_name: str | None = None, 

39) -> str: 

40 """Create a new backup 

41 

42 Parameters 

43 ---------- 

44 repo_root : Path 

45 The directory containing the GSB-managed repo 

46 tag_message : str, optional 

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

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

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

50 commit_message : str, optional 

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

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

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

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

55 parent: str, optional 

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

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

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

59 tag_name : str, optional 

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

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

62 a tag. 

63 

64 Returns 

65 ------- 

66 str 

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

68 tag name 

69 

70 Raises 

71 ------ 

72 OSError 

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

74 ValueError 

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

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

77 """ 

78 manifest = Manifest.of(repo_root) 

79 

80 if parent is not None: 

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

82 

83 _git.add(repo_root, manifest.patterns) 

84 _git.force_add(repo_root, REQUIRED_FILES) 

85 try: 

86 identifier = _git.commit( 

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

88 ).hash 

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

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

91 except ValueError: 

92 if not tag_message: 

93 raise 

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

95 if tag_message: 

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

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

98 if tag.target == head: 

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

100 identifier = _git.tag( 

101 repo_root, tag_name or generate_tag_name(), tag_message 

102 ).name 

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

104 return identifier