This repository has no description
4.7 kB
143 lines
1use knot_git::Repo;
2use knot_maintenance::{Options, PruneGrace, ReflogRetention, run_repo};
3use knot_types::{ObjectFormat, UnixSeconds};
4
5mod common;
6use common::{
7 EMPTY_TREE_SHA1, assert_fsck_clean, commit_on, create_repo, delete_ref, empty_tree,
8 has_cruft_pack, now, options, set_ref, set_reflog_seconds,
9};
10
11fn opts(grace: PruneGrace) -> Options {
12 Options {
13 prune_grace: grace,
14 commit_graph: false,
15 multi_pack_index: false,
16 bitmap: false,
17 ..options()
18 }
19}
20
21fn retain_repeat_then_expire(format: ObjectFormat) {
22 let scan = tempfile::tempdir().unwrap();
23 let empty = empty_tree(format);
24 let repo = create_repo(scan.path(), format, "did:plc:cuttle");
25
26 let base = commit_on(&repo, empty, 0, Vec::new());
27 let doomed = commit_on(&repo, empty, 1, vec![base]);
28 set_ref(&repo, "refs/heads/main", base);
29 set_ref(&repo, "refs/heads/feature", doomed);
30 run_repo(&repo, now(), &opts(PruneGrace::from_secs(86_400))).unwrap();
31 assert!(repo.contains(doomed), "doomed is packed while reachable");
32
33 delete_ref(&repo, "refs/heads/feature");
34 set_ref(
35 &repo,
36 "refs/heads/main",
37 commit_on(&repo, empty, 2, vec![base]),
38 );
39
40 let retained = run_repo(&repo, now(), &opts(PruneGrace::from_secs(86_400))).unwrap();
41 assert!(
42 retained.prune.crufted.get() >= 1,
43 "young unreachable objects are crufted, not dropped"
44 );
45 let reopened = Repo::open(repo.git().git_dir()).unwrap();
46 assert!(
47 has_cruft_pack(&reopened) && reopened.contains(doomed),
48 "a cruft pack holds the young object"
49 );
50 assert_fsck_clean(&reopened);
51
52 run_repo(&reopened, now(), &opts(PruneGrace::from_secs(86_400))).unwrap();
53 let recycled = Repo::open(repo.git().git_dir()).unwrap();
54 assert!(
55 recycled.contains(doomed),
56 "the young object survives a repeat cruft cycle within grace"
57 );
58 assert_fsck_clean(&recycled);
59
60 assert!(
61 run_repo(&recycled, now(), &opts(PruneGrace::from_secs(0)))
62 .unwrap()
63 .prune
64 .ran
65 );
66 let settled = Repo::open(repo.git().git_dir()).unwrap();
67 assert!(
68 !settled.contains(doomed),
69 "the past-grace object is finally dropped"
70 );
71 assert!(
72 settled.contains(base) && !has_cruft_pack(&settled),
73 "base survives, no cruft lingers"
74 );
75 assert_fsck_clean(&settled);
76}
77
78#[test]
79fn cruft_retains_survives_a_repeat_cycle_then_expires_sha1() {
80 retain_repeat_then_expire(ObjectFormat::SHA1);
81}
82
83#[test]
84fn cruft_retains_survives_a_repeat_cycle_then_expires_sha256() {
85 retain_repeat_then_expire(ObjectFormat::SHA256);
86}
87
88#[test]
89fn a_reflog_floor_above_the_retention_minimum_keeps_referenced_objects() {
90 let scan = tempfile::tempdir().unwrap();
91 let repo = create_repo(scan.path(), ObjectFormat::SHA1, "did:plc:whelk");
92
93 let base = commit_on(&repo, EMPTY_TREE_SHA1, 0, Vec::new());
94 let doomed = commit_on(&repo, EMPTY_TREE_SHA1, 1, vec![base]);
95 set_ref(&repo, "refs/heads/main", doomed);
96 set_ref(
97 &repo,
98 "refs/heads/main",
99 commit_on(&repo, EMPTY_TREE_SHA1, 2, vec![base]),
100 );
101
102 let opts = Options {
103 reflog_floor: ReflogRetention::from_secs(3_000_000_000),
104 ..opts(PruneGrace::from_secs(0))
105 };
106 run_repo(&repo, UnixSeconds::new(4_000_000_000), &opts).unwrap();
107
108 let reopened = Repo::open(repo.git().git_dir()).unwrap();
109 assert!(
110 reopened.contains(doomed),
111 "an object held only by a retained reflog entry is a prune root"
112 );
113}
114
115#[test]
116fn a_rewound_away_commit_survives_on_its_reflog_old_pointer_alone() {
117 let scan = tempfile::tempdir().unwrap();
118 let repo = create_repo(scan.path(), ObjectFormat::SHA1, "did:plc:periwinkle");
119
120 let lost = commit_on(&repo, EMPTY_TREE_SHA1, 7, Vec::new());
121 set_ref(&repo, "refs/heads/main", lost);
122 let survivor = commit_on(&repo, EMPTY_TREE_SHA1, 9, Vec::new());
123 set_ref(&repo, "refs/heads/main", survivor);
124
125 set_reflog_seconds(&repo, "refs/heads/main", &[1_000_000, 2_000_000_000]);
126
127 let opts = Options {
128 reflog_floor: ReflogRetention::from_secs(500_000_000),
129 ..opts(PruneGrace::from_secs(0))
130 };
131 run_repo(&repo, UnixSeconds::new(2_000_000_000), &opts).unwrap();
132
133 let reopened = Repo::open(repo.git().git_dir()).unwrap();
134 assert!(
135 reopened.contains(lost),
136 "a force-rewound commit reachable only through a reflog old-pointer must not be pruned"
137 );
138 assert!(
139 reopened.contains(survivor),
140 "the live tip survives the prune"
141 );
142 assert_fsck_clean(&reopened);
143}