1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! Conflict listing + resolution across project and user brains.
//! Split out of `project.rs` (v2.5.1); re-exported by [`crate::project`].
use std::path::Path;
use kimetsu_core::KimetsuResult;
use crate::conflict;
use crate::lock::ProjectLock;
use crate::project::*;
use crate::user_brain;
/// v0.5.2: list open conflict-detection hits across the project brain
/// and (when enabled) the user brain. Each `ConflictReport` carries a
/// `source` label so the CLI can render which brain originated it —
/// resolve takes a separate code path per brain since the row only
/// lives in one DB.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ScopedConflict {
/// Either "project" or "user". Determines which DB `resolve_conflict`
/// must target when the operator chooses to apply a resolution.
pub source: String,
#[serde(flatten)]
pub report: conflict::ConflictReport,
}
/// Merge open conflicts from project + user brains. `limit` is applied
/// per-brain, so the worst case is `limit * 2` rows returned — the CLI
/// can re-truncate on display if needed.
pub fn list_conflicts(start: &Path, limit: u32) -> KimetsuResult<Vec<ScopedConflict>> {
let mut out = Vec::new();
let (_paths, config, project_conn) = load_project_readonly(start)?;
for report in conflict::list_unresolved_conflicts(&project_conn, limit)? {
out.push(ScopedConflict {
source: "project".to_string(),
report,
});
}
// W3.3: honor config.kimetsu.use_user_brain with env override.
if let Some(user_conn) =
user_brain::open_user_brain_readonly_for_config(config.kimetsu.use_user_brain)?
{
for report in conflict::list_unresolved_conflicts(&user_conn, limit)? {
out.push(ScopedConflict {
source: "user".to_string(),
report,
});
}
}
out.sort_by(|a, b| b.report.detected_at.cmp(&a.report.detected_at));
Ok(out)
}
/// Resolve a single open conflict by id with one of `kept_new`,
/// `kept_existing`, or `kept_both`. The conflict can live in either
/// the project brain or the user brain — we try project first, and on
/// "not found" fall through to user. Returns Ok(true) if a row was
/// updated.
///
/// The explicit decision and losing-side retirement are one durable transaction.
pub fn resolve_conflict(start: &Path, conflict_id: &str, resolution: &str) -> KimetsuResult<bool> {
let (paths, config, project_conn) = load_project(start)?;
let _lock = ProjectLock::acquire(&paths, "brain memory conflict resolve", None)?;
if conflict::resolve_conflict(&project_conn, conflict_id, resolution)? {
return Ok(true);
}
drop(project_conn); // release before opening user brain (avoid pseudo-conflict on flock semantics)
// W3.3: honor config.kimetsu.use_user_brain with env override.
if let Some(user_conn) = user_brain::open_user_brain_for_config(config.kimetsu.use_user_brain)?
{
return conflict::resolve_conflict(&user_conn, conflict_id, resolution);
}
Ok(false)
}