Coverage for gsb/backup.py: 100%
32 statements
« prev ^ index » next coverage.py v7.2.6, created at 2024-09-08 16:23 -0400
« 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
6from . import _git
7from .logging import IMPORTANT
8from .manifest import MANIFEST_NAME, Manifest
10REQUIRED_FILES: tuple[Path, ...] = (Path(".gitignore"), Path(MANIFEST_NAME))
12LOGGER = logging.getLogger(__name__)
15def generate_tag_name() -> str:
16 """Generate a new calver-ish tag name
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
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")
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
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.
64 Returns
65 -------
66 str
67 An identifier for the backup in the form of either a commit hash or a
68 tag name
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)
80 if parent is not None:
81 _git.reset(repo_root, parent, hard=False)
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