use crate::constants::BACKUP_INFIX;
pub(crate) const BACKUP_OF_LINK_SKIPPED: &str =
"the target is a link; it is removed without copying what it points at";
pub(crate) const BACKUP_OF_SPECIAL_SKIPPED: &str =
"the target is not a regular file; it is removed without being copied";
pub(crate) const BACKUP_OF_BACKUP_SKIPPED: &str =
"the target is itself a backup copy; no copy of a copy is made";
pub(crate) const KEEP_BACKUP_REDUNDANT_FOR_DELETE: &str = "--keep-backup only preserves an existing delete copy; pass --backup or set [defaults].backup = true to create one";
pub(crate) const TRANSACTION_SNAPSHOT_SUPERSEDES_COPY: &str =
"the transaction already preserved this target: no second copy was made";
pub(crate) fn is_backup_timestamp_name(name: &str) -> bool {
use crate::constants::{
BACKUP_STAMP_DATE_DIGITS as DATE, BACKUP_STAMP_MILLIS_DIGITS as MILLIS,
BACKUP_STAMP_SEPARATOR as SEP, BACKUP_STAMP_TIME_DIGITS as TIME,
};
let Some((_, stamp)) = name.rsplit_once(BACKUP_INFIX) else {
return false;
};
let b = stamp.as_bytes();
let digits = |range: std::ops::Range<usize>| b[range].iter().all(u8::is_ascii_digit);
let time_end = DATE + 1 + TIME;
let millis_end = time_end + 1 + MILLIS;
if b.len() != time_end && b.len() != millis_end {
return false;
}
let base = digits(0..DATE) && b[DATE] == SEP && digits(DATE + 1..time_end);
if b.len() == time_end {
base
} else {
base && b[time_end] == SEP && digits(time_end + 1..millis_end)
}
}
pub(crate) fn parse_backup_timestamp(name: &str) -> Option<std::time::SystemTime> {
if !is_backup_timestamp_name(name) {
return None;
}
let (_, stamp) = name.rsplit_once(crate::constants::BACKUP_INFIX)?;
let bytes = stamp.as_bytes();
let number = |range: std::ops::Range<usize>| stamp.get(range)?.parse::<u64>().ok();
let year = number(0..4)?;
let month = number(4..6)?;
let day = number(6..8)?;
let hour = number(9..11)?;
let minute = number(11..13)?;
let second = number(13..15)?;
let millis = if bytes.len() > 16 { number(16..19)? } else { 0 };
if !(1..=12).contains(&month) || !(1..=31).contains(&day) || hour > 23 || minute > 59 {
return None;
}
let secs = crate::atomic::utc_to_epoch(
year as i64,
month as i64,
day as i64,
hour,
minute,
second.min(59),
);
Some(
std::time::SystemTime::UNIX_EPOCH
+ std::time::Duration::from_secs(secs)
+ std::time::Duration::from_millis(millis),
)
}
pub(crate) fn is_backup_path(path: &std::path::Path) -> bool {
path.file_name()
.and_then(|n| n.to_str())
.is_some_and(|name| name.contains(BACKUP_INFIX) || is_backup_timestamp_name(name))
}
#[must_use]
pub(crate) fn backup_exclude_globs() -> Vec<String> {
crate::constants::BACKUP_EXCLUDE_GLOBS
.iter()
.map(|s| (*s).to_string())
.collect()
}
pub(crate) fn push_backup_excludes(exclude: &mut Vec<String>) {
for g in crate::constants::BACKUP_EXCLUDE_GLOBS {
if !exclude.iter().any(|e| e == *g) {
exclude.push((*g).to_string());
}
}
}