Skip to content

Commit

Permalink
add import function
Browse files Browse the repository at this point in the history
originally posted at github.com/larkery/issues/137

Function to parse zsh's histfile and add it to the database.
Leverages zsh's history file parsing so it handles newlines and
arbitrary characters very well. The other tools suggested in the README
require other languages and don't handle multiline commands soundly.

Inserts are batched and it's pretty fast. A bit of time is wasted
calling `fc -l` to read the timestamps, but it takes ~1s to parse ~20k
lines for me so it should be fast enough for most use-cases.

Notes:

* this sets the `session` to `0`
  * (sqlite starts autoincrement ids at 1 so it shouldn't coincide with
    any actual histdb sessions)
* `dir` is set to the empty string (`''`) instead of `NULL` because
  sqlite doesn't let you use `NULL` as part of a key
* Added `unique(session, command_id, place_id, start_time) on conflict
  ignore` constraint to the `history` table so history instances are
  de-duped.
  * Useful for importing history file backups that likely contain dupes
  • Loading branch information
baodrate committed Mar 22, 2023
1 parent 4ca83bc commit 5e21e41
Showing 1 changed file with 54 additions and 1 deletion.
55 changes: 54 additions & 1 deletion sqlite-history.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ create table history (id integer primary key autoincrement,
place_id int references places (id),
exit_status int,
start_time real,
duration real
duration real,
unique(session, command_id, place_id, start_time) on conflict ignore
) strict;
PRAGMA user_version = 2;
EOF
Expand Down Expand Up @@ -136,6 +137,57 @@ where id = (select max(id) from history) and
EOF
}

_histdb_import() {
emulate -L zsh
setopt HIST_LEX_WORDS

local _HISTFILE=${1:-$HISTFILE}
# push current history list onto a stack and initialize a new one
fc -p -a
# read all the lines from the given histfile
local HISTFILE=$_HISTFILE
local HISTSIZE=999999999
local SAVEHIST=0
fc -R ${1:-$HISTFILE}
print "loaded ${#history} lines from history file: $HISTFILE" >&2

local -a histories
local -i i=0
# history is read into $history associative array, but we don't have access to
# timestamps/durations so parse the output of `fc -l` to get those
fc -l -t %s -d -D 0 | while { read -r histcmd timestamp duration _cmd } {
# the command output by `fc -l` is escaped so use $history[$histcmd]
# instead to get the raw, unescaped characters
# omit empty commands (not sure how this happens. `print -S` maybe?)
[[ -n "${history[$histcmd]}" ]] || continue
((++i))
# duration is formatted as m:ss so parse it back into integer seconds
# escape the history command
histories+=("(${timestamp}, $((${duration%:*}*60 + ${duration#*:})), '${history[$histcmd]//'/''}')")
}
_histdb_init
result=$(_histdb_query_batch <<EOF
insert into places (host, dir) values (${HISTDB_HOST}, '');
insert into commands (argv) values ${(@pj:,\n:)${(@)${(@uv)history//'/''}/#/('}/%/')};
with histories (timestamp, duration, cmd) as (values ${(pj:,\n:)histories})
insert into history (session, command_id, place_id, start_time, duration)
select
${SESSION:-0},
c.id,
(select id from places where host = ${HISTDB_HOST} and dir = ''),
h.timestamp,
h.duration
from histories h
join commands c on c.argv = h.cmd
-- on conflict (session, command_id, place_id, start_time) do update set duration = excluded.duration
returning id
;
EOF
) && print "added ${#${(@f)result}} history lines"
print -lr -- "[DONE] imported $i lines from $HISTFILE"
}
_histdb_addhistory () {
local -F started=$EPOCHREALTIME
local cmd="${1[0, -2]}"
Expand Down Expand Up @@ -175,6 +227,7 @@ EOF
add-zsh-hook zshaddhistory _histdb_addhistory
add-zsh-hook precmd _histdb_update_outcome
add-zsh-hook zshexit _histdb_update_outcome
histdb-top () {
_histdb_init
Expand Down

0 comments on commit 5e21e41

Please sign in to comment.