Skip to content

Commit 6b8e22d

Browse files
committed
feat: delete duplicate history
1 parent a45b4c5 commit 6b8e22d

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

Diff for: crates/atuin-client/src/database.rs

+22
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ pub trait Database: Send + Sync + 'static {
119119
async fn all_with_count(&self) -> Result<Vec<(History, i32)>>;
120120

121121
async fn stats(&self, h: &History) -> Result<HistoryStats>;
122+
123+
async fn get_dups(&self, before: i64, dupkeep: u32) -> Result<Vec<History>>;
122124
}
123125

124126
// Intended for use on a developer machine and not a sync server.
@@ -768,6 +770,26 @@ impl Database for Sqlite {
768770
duration_over_time,
769771
})
770772
}
773+
774+
async fn get_dups(&self, before: i64, dupkeep: u32) -> Result<Vec<History>> {
775+
let res = sqlx::query(
776+
"SELECT * FROM (
777+
SELECT *, ROW_NUMBER()
778+
OVER (PARTITION BY command, cwd, hostname ORDER BY timestamp DESC)
779+
AS rn
780+
FROM history
781+
) sub
782+
WHERE rn > ?1 and timestamp < ?2;
783+
",
784+
)
785+
.bind(dupkeep)
786+
.bind(before)
787+
.map(Self::query_history)
788+
.fetch_all(&self.pool)
789+
.await?;
790+
791+
Ok(res)
792+
}
771793
}
772794

773795
trait SqlBuilderExt {

Diff for: crates/atuin/src/command/client/history.rs

+76
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ pub enum Cmd {
117117
#[arg(short = 'n', long)]
118118
dry_run: bool,
119119
},
120+
121+
/// Delete duplicate history entries (that have the same command, cwd and hostname)
122+
Dedup {
123+
/// List matching history lines without performing the actual deletion.
124+
#[arg(short = 'n', long)]
125+
dry_run: bool,
126+
127+
/// Only delete results added before this date
128+
#[arg(long, short)]
129+
before: String,
130+
131+
/// How many recent duplicates to keep
132+
#[arg(long)]
133+
dupkeep: u32,
134+
}
120135
}
121136

122137
#[derive(Clone, Copy, Debug)]
@@ -544,6 +559,58 @@ impl Cmd {
544559
Ok(())
545560
}
546561

562+
async fn handle_dedup(
563+
db: &impl Database,
564+
settings: &Settings,
565+
store: SqliteStore,
566+
before: i64,
567+
dupkeep: u32,
568+
dry_run: bool,
569+
) -> Result<()> {
570+
// Grab all executed commands and filter them using History::should_save.
571+
// We could iterate or paginate here if memory usage becomes an issue.
572+
let matches: Vec<History> = db
573+
.get_dups(before, dupkeep)
574+
.await?;
575+
576+
match matches.len() {
577+
0 => {
578+
println!("No duplicates to delete.");
579+
return Ok(());
580+
}
581+
1 => println!("Found 1 duplicate to delete."),
582+
n => println!("Found {n} duplicates to delete."),
583+
}
584+
585+
if dry_run {
586+
print_list(
587+
&matches,
588+
ListMode::Human,
589+
Some(settings.history_format.as_str()),
590+
false,
591+
false,
592+
settings.timezone,
593+
);
594+
} else {
595+
let encryption_key: [u8; 32] = encryption::load_key(settings)
596+
.context("could not load encryption key")?
597+
.into();
598+
let host_id = Settings::host_id().expect("failed to get host_id");
599+
let history_store = HistoryStore::new(store.clone(), host_id, encryption_key);
600+
601+
for entry in matches {
602+
eprintln!("deleting {}", entry.id);
603+
if settings.sync.records {
604+
let (id, _) = history_store.delete(entry.id).await?;
605+
history_store.incremental_build(db, &[id]).await?;
606+
} else {
607+
db.delete(entry).await?;
608+
}
609+
}
610+
}
611+
Ok(())
612+
}
613+
547614
pub async fn run(self, settings: &Settings) -> Result<()> {
548615
let context = current_context();
549616

@@ -628,6 +695,15 @@ impl Cmd {
628695
Self::Prune { dry_run } => {
629696
Self::handle_prune(&db, settings, store, context, dry_run).await
630697
}
698+
699+
Self::Dedup { dry_run, before, dupkeep } => {
700+
let before = interim::parse_date_string(
701+
before.as_str(),
702+
OffsetDateTime::now_utc(),
703+
interim::Dialect::Uk,
704+
)?.unix_timestamp_nanos() as i64;
705+
Self::handle_dedup(&db, settings, store, before, dupkeep, dry_run).await
706+
}
631707
}
632708
}
633709
}

0 commit comments

Comments
 (0)