Coverage for gsb/rewind.py: 100%
28 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 21:03 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 21:03 +0000
1"""Functionality for restoring to an old backup"""
3import logging
4from pathlib import Path
6from . import _git, backup, manifest
7from .logging import IMPORTANT
9LOGGER = logging.getLogger(__name__)
12def generate_restore_tag_name(revision: str) -> str:
13 """Generate a new calver-ish tag name
15 Parameters
16 ----------
17 revision : str
18 The commit hash or tag name of the backup to restore
20 Returns
21 -------
22 str
23 A tag name that indicates both the time a backup was restored and the
24 identifier of the original revision
25 """
26 return f"{backup.generate_tag_name()}.restore_of_{revision}"
29def restore_backup(
30 repo_root: Path, revision: str, keep_gsb_files: bool = True, hard: bool = False
31) -> str:
32 """Rewind to a previous backup state and create a new backup
34 Parameters
35 ----------
36 repo_root : Path
37 The directory containing the GSB-managed repo
38 revision : str
39 The commit hash or tag name of the backup to restore
40 keep_gsb_files : bool, optional
41 By default, `.gsb_manifest` and `.gitignore` *will not* be restored
42 (that is, the latest versions will be kept). To override this behavior,
43 pass in `keep_gsb_files = False`.
44 hard : bool, optional
45 By default, any backups after the specified revision will be kept in
46 the history (with the specified restore point just copied to the top
47 of the stack). If you want to *fully erase* all progress after the
48 restore point (backed up or loose), pass in `hard = True` and then run
49 `git gc --aggressive --prune=now` afterward.
51 Returns
52 -------
53 str
54 The name of the restored backup
56 Notes
57 -----
58 - Before creating the backup, any un-backed up changes will first be backed up
59 unless `hard = True` is specified.
60 - When restoring with `hard = True`, **unsaved changes** to `.gsb_manifest`
61 and `.gitignore` will **not** be kept, even with `keep_gsb_files = True`.
62 Instead, the restored backup will contain the GSB files from the most
63 recent backup (tagged or otherwise).
65 Raises
66 ------
67 OSError
68 If the specified repo does not exist or is not a GSB-managed repo
69 ValueError
70 If the specified revision does not exist
71 """
72 _git.show(repo_root, revision) # ensure revision exists
74 orig_head = _git.show(repo_root, "HEAD").hash # type: ignore[union-attr]
76 if hard:
77 _git.add(repo_root, manifest.Manifest.of(repo_root).patterns)
78 else:
79 LOGGER.log(
80 IMPORTANT, "Backing up any unsaved changes before rewinding to %s", revision
81 )
82 try:
83 orig_head = backup.create_backup(
84 repo_root,
85 f"Backing up state before rewinding to {revision}",
86 )
87 except ValueError: # nothing to back up
88 pass
90 _git.reset(repo_root, revision, hard=True)
91 if keep_gsb_files:
92 _git.checkout_files(repo_root, orig_head, backup.REQUIRED_FILES)
93 if hard:
94 try: # on the off chance that the gsb files changed, commit them
95 backup.create_backup(
96 repo_root, commit_message="Cherry-picking changes to the gsb files"
97 )
98 except ValueError: # this is actually the more likely outcome
99 pass
100 return revision
102 _git.reset(repo_root, orig_head, hard=False)
103 return backup.create_backup(
104 repo_root,
105 f"Restored to {revision}",
106 tag_name=generate_restore_tag_name(revision),
107 )