Skip to content

Commit 30096ef

Browse files
committed
fix bug with repeated excludes from restic
restic can return repeated excluded paths for a snapshot which resulted in a primary key constraint failure when inserting. Here we turn paths, excludes and tags into sets to ensure uniqueness.
1 parent e5f58fd commit 30096ef

File tree

2 files changed

+36
-22
lines changed

2 files changed

+36
-22
lines changed

src/cache/tests.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
2-
cmp::Reverse, convert::Infallible, env, fs, iter, mem, path::PathBuf,
2+
cmp::Reverse, collections::HashSet, convert::Infallible, env, fs, iter,
3+
mem, path::PathBuf,
34
};
45

56
use camino::{Utf8Path, Utf8PathBuf};
@@ -323,21 +324,23 @@ fn cache_snapshots_entries() {
323324
assert_eq!(s0.original_id, s1.original_id);
324325
assert_eq!(s0.program_version, s1.program_version);
325326

326-
let mut s0_paths: Vec<String> = s0.paths.to_vec();
327+
let mut s0_paths: Vec<String> = s0.paths.iter().cloned().collect();
327328
s0_paths.sort();
328-
let mut s1_paths: Vec<String> = s1.paths.to_vec();
329+
let mut s1_paths: Vec<String> = s1.paths.iter().cloned().collect();
329330
s1_paths.sort();
330331
assert_eq!(s0_paths, s1_paths);
331332

332-
let mut s0_excludes: Vec<String> = s0.excludes.to_vec();
333+
let mut s0_excludes: Vec<String> =
334+
s0.excludes.iter().cloned().collect();
333335
s0_excludes.sort();
334-
let mut s1_excludes: Vec<String> = s1.excludes.to_vec();
336+
let mut s1_excludes: Vec<String> =
337+
s1.excludes.iter().cloned().collect();
335338
s1_excludes.sort();
336339
assert_eq!(s0_excludes, s1_excludes);
337340

338-
let mut s0_tags: Vec<String> = s0.tags.to_vec();
341+
let mut s0_tags: Vec<String> = s0.tags.iter().cloned().collect();
339342
s0_tags.sort();
340-
let mut s1_tags: Vec<String> = s1.tags.to_vec();
343+
let mut s1_tags: Vec<String> = s1.tags.iter().cloned().collect();
341344
s1_tags.sort();
342345
assert_eq!(s0_tags, s1_tags);
343346
}
@@ -355,7 +358,9 @@ fn cache_snapshots_entries() {
355358
"/home/user".to_string(),
356359
"/etc".to_string(),
357360
"/var".to_string(),
358-
],
361+
]
362+
.into_iter()
363+
.collect(),
359364
hostname: Some("foo.com".to_string()),
360365
username: Some("user".to_string()),
361366
uid: Some(123),
@@ -364,8 +369,12 @@ fn cache_snapshots_entries() {
364369
".cache".to_string(),
365370
"Cache".to_string(),
366371
"/home/user/Downloads".to_string(),
367-
],
368-
tags: vec!["foo_machine".to_string(), "rewrite".to_string()],
372+
]
373+
.into_iter()
374+
.collect(),
375+
tags: vec!["foo_machine".to_string(), "rewrite".to_string()]
376+
.into_iter()
377+
.collect(),
369378
original_id: Some("fefwfwew".to_string()),
370379
program_version: Some("restic 0.16.0".to_string()),
371380
};
@@ -375,7 +384,7 @@ fn cache_snapshots_entries() {
375384
time: mk_datetime(2025, 5, 12, 17, 00, 00),
376385
parent: Some("wat".to_string()),
377386
tree: "anothertree".to_string(),
378-
paths: vec!["/home/user".to_string()],
387+
paths: vec!["/home/user".to_string()].into_iter().collect(),
379388
hostname: Some("foo.com".to_string()),
380389
username: Some("user".to_string()),
381390
uid: Some(123),
@@ -384,8 +393,12 @@ fn cache_snapshots_entries() {
384393
".cache".to_string(),
385394
"Cache".to_string(),
386395
"/home/user/Downloads".to_string(),
387-
],
388-
tags: vec!["foo_machine".to_string(), "rewrite".to_string()],
396+
]
397+
.into_iter()
398+
.collect(),
399+
tags: vec!["foo_machine".to_string(), "rewrite".to_string()]
400+
.into_iter()
401+
.collect(),
389402
original_id: Some("fefwfwew".to_string()),
390403
program_version: Some("restic 0.16.0".to_string()),
391404
};
@@ -395,13 +408,13 @@ fn cache_snapshots_entries() {
395408
time: mk_datetime(2023, 5, 12, 17, 00, 00),
396409
parent: None,
397410
tree: "fwefwfwwefwefwe".to_string(),
398-
paths: vec![],
411+
paths: HashSet::new(),
399412
hostname: None,
400413
username: None,
401414
uid: None,
402415
gid: None,
403-
excludes: vec![],
404-
tags: vec![],
416+
excludes: HashSet::new(),
417+
tags: HashSet::new(),
405418
original_id: None,
406419
program_version: None,
407420
};
@@ -460,13 +473,13 @@ fn lots_of_snapshots() {
460473
time: timestamp_to_datetime(i as i64).unwrap(),
461474
parent: None,
462475
tree: i.to_string(),
463-
paths: vec![],
476+
paths: HashSet::new(),
464477
hostname: None,
465478
username: None,
466479
uid: None,
467480
gid: None,
468-
excludes: vec![],
469-
tags: vec![],
481+
excludes: HashSet::new(),
482+
tags: HashSet::new(),
470483
original_id: None,
471484
program_version: None,
472485
};

src/restic.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use core::str;
33
use std::os::unix::process::CommandExt;
44
use std::{
55
borrow::Cow,
6+
collections::HashSet,
67
ffi::OsStr,
78
fmt::{self, Display, Formatter},
89
io::{self, BufRead, BufReader, Lines, Read},
@@ -313,7 +314,7 @@ pub struct Snapshot {
313314
#[serde(default)]
314315
pub parent: Option<String>,
315316
pub tree: String,
316-
pub paths: Vec<String>,
317+
pub paths: HashSet<String>,
317318
#[serde(default)]
318319
pub hostname: Option<String>,
319320
#[serde(default)]
@@ -323,9 +324,9 @@ pub struct Snapshot {
323324
#[serde(default)]
324325
pub gid: Option<u32>,
325326
#[serde(default)]
326-
pub excludes: Vec<String>,
327+
pub excludes: HashSet<String>,
327328
#[serde(default)]
328-
pub tags: Vec<String>,
329+
pub tags: HashSet<String>,
329330
#[serde(default)]
330331
pub original_id: Option<String>,
331332
#[serde(default)]

0 commit comments

Comments
 (0)