Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 12 additions & 20 deletions src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
use anyhow::{bail, Result};
use chrono::Utc;

use crate::context::CommandContext;
use crate::db::Db;
use crate::git_utils;
use crate::types::validate_key;

const CONFIG_PREFIX: &str = "meta:";

pub fn run(list: bool, unset: bool, key: Option<&str>, value: Option<&str>) -> Result<()> {
let repo = git_utils::discover_repo()?;
let db_path = git_utils::db_path(&repo)?;
let db = Db::open(&db_path)?;
let ctx = CommandContext::open_gix(None)?;

if list {
return run_list(&db);
return run_list(&ctx.db);
}

if unset {
let key = key.ok_or_else(|| anyhow::anyhow!("--unset requires a key"))?;
validate_config_key(key)?;
return run_unset(&repo, &db, key);
return run_unset(&ctx, key);
}

let key = key.ok_or_else(|| anyhow::anyhow!("key is required"))?;
validate_config_key(key)?;

match value {
Some(val) => run_set(&repo, &db, key, val),
None => run_get(&db, key),
Some(val) => run_set(&ctx, key, val),
None => run_get(&ctx.db, key),
}
}

Expand All @@ -43,19 +40,17 @@ fn validate_config_key(key: &str) -> Result<()> {
Ok(())
}

fn run_set(repo: &gix::Repository, db: &Db, key: &str, value: &str) -> Result<()> {
let email = git_utils::get_email(repo)?;
let timestamp = Utc::now().timestamp_millis();
fn run_set(ctx: &CommandContext, key: &str, value: &str) -> Result<()> {
let stored_value = serde_json::to_string(value)?;

db.set(
ctx.db.set(
"project",
"",
key,
&stored_value,
"string",
&email,
timestamp,
&ctx.email,
ctx.timestamp,
)?;
Ok(())
}
Expand Down Expand Up @@ -86,11 +81,8 @@ fn run_list(db: &Db) -> Result<()> {
Ok(())
}

fn run_unset(repo: &gix::Repository, db: &Db, key: &str) -> Result<()> {
let email = git_utils::get_email(repo)?;
let timestamp = Utc::now().timestamp_millis();

let removed = db.rm("project", "", key, &email, timestamp)?;
fn run_unset(ctx: &CommandContext, key: &str) -> Result<()> {
let removed = ctx.db.rm("project", "", key, &ctx.email, ctx.timestamp)?;
if !removed {
eprintln!("key '{}' not found", key);
}
Expand Down
26 changes: 15 additions & 11 deletions src/commands/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use git2::Repository;
use serde_json::{json, Map, Value};

use crate::context::CommandContext;
use crate::db::Db;
use crate::git_utils;
use crate::list_value::list_values_from_json;
Expand All @@ -17,13 +18,12 @@ pub fn run(
) -> Result<()> {
let mut target = Target::parse(target_str)?;

let repo = git_utils::git2_discover_repo()?;
target.git2_resolve(&repo)?;
let db_path = git_utils::git2_db_path(&repo)?;
let db = Db::open(&db_path)?;
let ctx = CommandContext::open_git2(None)?;
ctx.git2_resolve_target(&mut target)?;
let repo = ctx.git2_repo()?;

let include_target_subtree = target.target_type == TargetType::Path;
let mut entries = db.get_all_with_target_prefix(
let mut entries = ctx.db.get_all_with_target_prefix(
target.type_str(),
target.value_str(),
include_target_subtree,
Expand All @@ -33,10 +33,14 @@ pub fn run(
// If no exact match, try prefix expansion for non-commit types
// (commits are already resolved by git, but change-ids/branches may be partial)
if entries.is_empty() && target.target_type != TargetType::Path {
let matches = db.find_target_values_by_prefix(target.type_str(), target.value_str(), 2)?;
let matches =
ctx.db
.find_target_values_by_prefix(target.type_str(), target.value_str(), 2)?;
if matches.len() == 1 {
let expanded = &matches[0];
entries = db.get_all_with_target_prefix(target.type_str(), expanded, false, key)?;
entries = ctx
.db
.get_all_with_target_prefix(target.type_str(), expanded, false, key)?;
if !entries.is_empty() {
eprintln!("expanded to {}:{}", target.type_str(), expanded);
}
Expand All @@ -61,10 +65,10 @@ pub fn run(
.collect();

if !promised.is_empty() {
let hydrated = hydrate_promised_entries(&repo, &db, target.type_str(), &promised)?;
let hydrated = hydrate_promised_entries(repo, &ctx.db, target.type_str(), &promised)?;
if hydrated > 0 {
// Re-query to get the now-resolved values
entries = db.get_all_with_target_prefix(
entries = ctx.db.get_all_with_target_prefix(
target.type_str(),
target.value_str(),
include_target_subtree,
Expand All @@ -80,7 +84,7 @@ pub fn run(
.map(
|(entry_target_value, key, value, value_type, is_git_ref, _)| {
if is_git_ref {
let resolved_value = resolve_git_ref(&repo, &value)?;
let resolved_value = resolve_git_ref(repo, &value)?;
// JSON-encode the resolved content to match normal string format
let json_value = serde_json::to_string(&resolved_value)?;
Ok((entry_target_value, key, json_value, value_type))
Expand All @@ -96,7 +100,7 @@ pub fn run(
}

if json_output {
print_json(&db, &target, &resolved, with_authorship)?;
print_json(&ctx.db, &target, &resolved, with_authorship)?;
} else {
print_plain(&target, &resolved, key.is_some() && resolved.len() == 1)?;
}
Expand Down
59 changes: 25 additions & 34 deletions src/commands/import.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::collections::HashSet;

use anyhow::{bail, Context, Result};
use chrono::Utc;
use git2::Repository;
use serde_json::Value;

use crate::context::CommandContext;
use crate::db::Db;
use crate::git_utils;
use crate::types::GIT_REF_THRESHOLD;

pub fn run(format: &str, dry_run: bool, since: Option<&str>) -> Result<()> {
Expand All @@ -30,21 +29,17 @@ pub fn run(format: &str, dry_run: bool, since: Option<&str>) -> Result<()> {
}

fn run_entire(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
let repo = git_utils::git2_discover_repo()?;
let email = git_utils::git2_get_email(&repo)?;
let fallback_ts = Utc::now().timestamp_millis();
let ctx = CommandContext::open_git2(None)?;
let repo = ctx.git2_repo()?;
let email = &ctx.email;
let fallback_ts = ctx.timestamp;

let db_path = git_utils::git2_db_path(&repo)?;
let db = if dry_run {
None
} else {
Some(Db::open(&db_path)?)
};
let db = if dry_run { None } else { Some(&ctx.db) };

let mut imported_count = 0u64;

// Resolve the checkpoints tree (local or remote)
let checkpoints_tree = resolve_entire_ref(&repo, "entire/checkpoints/v1")?;
let checkpoints_tree = resolve_entire_ref(repo, "entire/checkpoints/v1")?;
if checkpoints_tree.is_none() {
eprintln!("No entire/checkpoints/v1 ref found (local or remote), skipping checkpoints");
}
Expand All @@ -64,13 +59,13 @@ fn run_entire(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
eprintln!("Scanning commits for Entire-Checkpoint trailers...");
}
imported_count +=
import_checkpoints_from_commits(&repo, cp_tree, &db, &email, dry_run, since_epoch)?;
import_checkpoints_from_commits(repo, cp_tree, db, email, dry_run, since_epoch)?;
}

// Step 2: Import trails
if let Some(tree) = resolve_entire_ref(&repo, "entire/trails/v1")? {
if let Some(tree) = resolve_entire_ref(repo, "entire/trails/v1")? {
eprintln!("Processing entire/trails/v1...");
imported_count += import_trails(&repo, &tree, &db, &email, fallback_ts, dry_run)?;
imported_count += import_trails(repo, &tree, db, email, fallback_ts, dry_run)?;
} else {
eprintln!("No entire/trails/v1 ref found, skipping trails");
}
Expand Down Expand Up @@ -107,7 +102,7 @@ fn resolve_entire_ref<'a>(repo: &'a Repository, refname: &str) -> Result<Option<
fn import_checkpoints_from_commits(
repo: &Repository,
checkpoints_tree: &git2::Tree,
db: &Option<Db>,
db: Option<&Db>,
email: &str,
dry_run: bool,
since_epoch: Option<i64>,
Expand Down Expand Up @@ -298,7 +293,7 @@ fn import_checkpoints_from_commits(
fn import_session(
repo: &Repository,
session_tree: &git2::Tree,
db: &Option<Db>,
db: Option<&Db>,
commit_sha: &str,
key_prefix: &str,
email: &str,
Expand Down Expand Up @@ -507,7 +502,7 @@ fn entry_to_tree<'a>(
}

/// Load the set of trail IDs that have already been imported.
fn load_imported_trail_ids(db: &Option<Db>) -> Result<HashSet<String>> {
fn load_imported_trail_ids(db: Option<&Db>) -> Result<HashSet<String>> {
let mut ids = HashSet::new();
if let Some(db) = db {
let mut stmt = db.conn.prepare(
Expand All @@ -527,7 +522,7 @@ fn load_imported_trail_ids(db: &Option<Db>) -> Result<HashSet<String>> {
fn import_trails(
repo: &Repository,
root_tree: &git2::Tree,
db: &Option<Db>,
db: Option<&Db>,
email: &str,
base_ts: i64,
dry_run: bool,
Expand Down Expand Up @@ -704,7 +699,7 @@ fn import_trails(
/// Large string values (> GIT_REF_THRESHOLD bytes) are stored as git blob refs.
fn set_value(
repo: &Repository,
db: &Option<Db>,
db: Option<&Db>,
dry_run: bool,
target_type: &str,
target_value: &str,
Expand Down Expand Up @@ -814,15 +809,11 @@ fn truncate(s: &str, max: usize) -> String {
const NOTES_REFS: &[&str] = &["refs/remotes/notes/ai", "refs/notes/ai"];

fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
let repo = git_utils::git2_discover_repo()?;
let email = git_utils::git2_get_email(&repo)?;
let ctx = CommandContext::open_git2(None)?;
let repo = ctx.git2_repo()?;
let email = &ctx.email;

let db_path = git_utils::git2_db_path(&repo)?;
let db = if dry_run {
None
} else {
Some(Db::open(&db_path)?)
};
let db = if dry_run { None } else { Some(&ctx.db) };

// Locate the notes ref — prefer remote mirror, fall back to local.
let notes_ref = NOTES_REFS
Expand Down Expand Up @@ -938,7 +929,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {

// Check whether we already have data for this commit so we can
// report skips without touching the DB on a real run.
if let Some(ref db) = db {
if let Some(db) = db {
if db.get("commit", &commit_sha, "agent.blame")?.is_some() {
skipped_exists += 1;
continue;
Expand All @@ -956,7 +947,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
},
);

if let Some(ref db) = db {
if let Some(db) = db {
// agent.blame — store as git blob ref if large
let (blame_val, is_ref) = if parsed.blame.len() > GIT_REF_THRESHOLD {
let oid = repo.blob(parsed.blame.as_bytes())?;
Expand All @@ -971,7 +962,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
"agent.blame",
&blame_val,
"string",
&email,
email,
commit_ts,
is_ref,
)?;
Expand All @@ -982,7 +973,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
"agent.git-ai.schema-version",
&json_string(&parsed.schema_version),
"string",
&email,
email,
commit_ts,
)?;

Expand All @@ -993,7 +984,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
"agent.git-ai.version",
&json_string(ver),
"string",
&email,
email,
commit_ts,
)?;
}
Expand All @@ -1005,7 +996,7 @@ fn run_git_ai(dry_run: bool, since_epoch: Option<i64>) -> Result<()> {
"agent.model",
&json_string(&parsed.model),
"string",
&email,
email,
commit_ts,
)?;
}
Expand Down
14 changes: 6 additions & 8 deletions src/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use std::collections::BTreeMap;
use anyhow::Result;
use chrono::{Duration, Utc};

use crate::context::CommandContext;
use crate::db::Db;
use crate::git_utils;
use crate::list_value::list_values_from_json;

// ── ANSI colours ──────────────────────────────────────────────────────────────
Expand All @@ -27,21 +27,19 @@ pub fn run(
timeline: bool,
promisor: bool,
) -> Result<()> {
let repo = git_utils::discover_repo()?;
let db_path = git_utils::db_path(&repo)?;
let db = Db::open(&db_path)?;
let ctx = CommandContext::open_gix(None)?;

if promisor {
return run_promisor_list(&db, target_type);
return run_promisor_list(&ctx.db, target_type);
}

if timeline {
return run_timeline(&db);
return run_timeline(&ctx.db);
}

match target_type {
None => run_overview(&db),
Some(tt) => run_list(&db, tt, term),
None => run_overview(&ctx.db),
Some(tt) => run_list(&ctx.db, tt, term),
}
}

Expand Down
Loading
Loading