Skip to content

Commit c27926d

Browse files
authored
feat: add tags for snapshot (#23)
* add tags for snapshot Signed-off-by: kerthcet <kerthcet@gmail.com> * address comments Signed-off-by: kerthcet <kerthcet@gmail.com> * update tests Signed-off-by: kerthcet <kerthcet@gmail.com> --------- Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 834e658 commit c27926d

3 files changed

Lines changed: 481 additions & 67 deletions

File tree

docs/proposals/SNAPSHOTS.md

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,20 @@ We use Git's storage model (hierarchical trees, content-addressable) but with sn
4747
└─────────────────────────────────────────────────────────┘
4848
4949
┌────────────────────────────────┐
50-
│ Filesystem Storage │
51-
│ .snapshots/ │
52-
│ ├── objects/ │
53-
│ │ ├── ab/ │
54-
│ │ │ └── cdef123... │
55-
│ │ └── 12/ │
56-
│ │ └── 3456... │
57-
│ ├── snapshots/ │
58-
│ │ ├── snap-uuid-1.json │
59-
│ │ └── snap-uuid-2.json │
60-
│ └── HEAD │
50+
│ Filesystem Storage │
51+
│ .snapshots/ │
52+
│ ├── objects/ │
53+
│ │ ├── ab/ │
54+
│ │ │ └── cdef123... │
55+
│ │ └── 12/ │
56+
│ │ └── 3456... │
57+
│ ├── snapshots/ │
58+
│ │ ├── snap-uuid-1.json │
59+
│ │ └── snap-uuid-2.json │
60+
│ └── refs/ │
61+
│ └── tags/ │
62+
│ ├── v1.0.0 │
63+
│ └── stable │
6164
└────────────────────────────────┘
6265
```
6366

@@ -127,8 +130,30 @@ objects/
127130
└── 887766... ← Tree: root directory structure (JSON)
128131
129132
snapshots/snap-uuid.json → points to root tree (998877...)
133+
134+
refs/tags/v1.0.0 → contains: snap-uuid (plain text file)
135+
refs/tags/stable → contains: snap-uuid
130136
```
131137

138+
**Tag System (Git-Style):**
139+
140+
Tags are stored as plain text files in `refs/tags/`:
141+
142+
```
143+
refs/tags/
144+
├── v1.0.0 ← Contains: "snap-abc-123-def"
145+
├── stable ← Contains: "snap-xyz-789"
146+
└── pre-deploy ← Contains: "snap-uvw-456"
147+
```
148+
149+
**Tag properties:**
150+
- **Immutable**: Once created, a tag cannot be changed (like Git tags)
151+
- **O(1) lookup**: Finding snapshots by tag requires reading one small file
152+
- **Stored in both places**: Tag names are in snapshot JSON *and* tag ref files
153+
- Snapshot file: Contains list of tags for fast delete
154+
- Tag refs: Enable O(1) tag → snapshot lookup
155+
- **Automatically cleaned up**: Deleting a snapshot removes its tag refs
156+
132157
---
133158

134159
## Core API
@@ -159,18 +184,20 @@ impl SnapshotManager {
159184
) -> Result<()>;
160185

161186
/// List all snapshots (optionally filtered by tags)
187+
/// Filter by tags uses O(1) tag ref lookup
162188
pub async fn list_snapshots(
163189
&self,
164-
filter_tags: Option<Vec<String>>,
190+
tags: Option<Vec<String>>, // OR filter: any matching tag
165191
) -> Result<Vec<SnapshotInfo>>;
166192

167-
/// Find snapshots by tag
168-
pub async fn find_by_tag(&self, tag: &str) -> Result<Vec<SnapshotInfo>>;
193+
/// Find snapshot by tag (O(1) lookup via tag ref)
194+
/// Returns single snapshot since tags are immutable
195+
pub async fn find_by_tag(&self, tag: &str) -> Result<Option<SnapshotInfo>>;
169196

170197
/// Get snapshot by ID
171198
pub async fn get_snapshot(&self, id: &str) -> Result<Snapshot>;
172199

173-
/// Delete snapshot
200+
/// Delete snapshot (also removes tag refs)
174201
pub async fn delete_snapshot(&self, id: &str) -> Result<()>;
175202
}
176203
```
@@ -247,8 +274,15 @@ async fn main() -> Result<()> {
247274
println!("{}: {} (tags: {:?})", snap.id, snap.message, snap.tags);
248275
}
249276

250-
// Find snapshots by tag
251-
let pre_task_snapshots = manager.find_by_tag("pre-task").await?;
277+
// Find snapshot by tag (O(1) lookup)
278+
if let Some(snapshot) = manager.find_by_tag("pre-task").await? {
279+
println!("Found: {}", snapshot.id);
280+
}
281+
282+
// Filter snapshots by tags
283+
let filtered = manager.list_snapshots(
284+
Some(vec!["pre-task".to_string(), "important".to_string()])
285+
).await?; // Returns snapshots with "pre-task" OR "important"
252286

253287
// Get specific snapshot details
254288
let snapshot = manager.get_snapshot(&snapshot_id).await?;

examples/snapshot_simple.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,14 @@ async fn main() -> Result<()> {
5858
println!(" Files: {}, Size: {} bytes", snap.file_count, snap.total_size);
5959
}
6060

61-
// 6. Filter by tag
62-
println!("\n6. Finding snapshots with 'init' tag:");
63-
let init_snapshots = manager.find_by_tag("init").await?;
64-
for snap in &init_snapshots {
61+
// 6. Find by tag (returns single snapshot since tags are immutable)
62+
println!("\n6. Finding snapshot with 'init' tag:");
63+
if let Some(snap) = manager.find_by_tag("init").await? {
6564
println!(" {} - {}", snap.id, snap.message);
6665
}
6766

68-
println!("\n7. Finding snapshots with 'feature' tag:");
69-
let feature_snapshots = manager.find_by_tag("feature").await?;
70-
for snap in &feature_snapshots {
67+
println!("\n7. Finding snapshot with 'feature' tag:");
68+
if let Some(snap) = manager.find_by_tag("feature").await? {
7169
println!(" {} - {}", snap.id, snap.message);
7270
}
7371

0 commit comments

Comments
 (0)