Coverage for gsb/backup.py: 100%
32 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-08 20:16 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-08 20:16 +0000
1"""Functionality for creating backups"""
3import datetime as dt
4import logging
5from pathlib import Path
7from . import _git
8from .logging import IMPORTANT
9from .manifest import MANIFEST_NAME, Manifest
11REQUIRED_FILES: tuple[Path, ...] = (Path(".gitignore"), Path(MANIFEST_NAME))
13LOGGER = logging.getLogger(__name__)
16def generate_tag_name() -> str:
17 """Generate a new calver-ish tag name
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
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")
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
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.
65 Returns
66 -------
67 str
68 An identifier for the backup in the form of either a commit hash or a
69 tag name
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)
81 if parent is not None:
82 _git.reset(repo_root, parent, hard=False)
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