feat(diff): fix diff bug in apply
This commit is contained in:
parent
f23a9b2653
commit
bee2ceff00
2 changed files with 166 additions and 78 deletions
|
|
@ -251,6 +251,9 @@ pub fn run(config_path: Option<PathBuf>, dry_run: bool, prune: bool) -> anyhow::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dry_run {
|
||||||
|
println!("\nRun without --dry-run to resolve conflicts.");
|
||||||
|
} else {
|
||||||
println!("\nHow to resolve conflicts?");
|
println!("\nHow to resolve conflicts?");
|
||||||
println!(" [s] Use source (overwrite target)");
|
println!(" [s] Use source (overwrite target)");
|
||||||
println!(" [t] Keep target (skip these files)");
|
println!(" [t] Keep target (skip these files)");
|
||||||
|
|
@ -300,9 +303,36 @@ pub fn run(config_path: Option<PathBuf>, dry_run: bool, prune: bool) -> anyhow::
|
||||||
}
|
}
|
||||||
"d" => {
|
"d" => {
|
||||||
let full_source = source_dir.join(&dotfile.source);
|
let full_source = source_dir.join(&dotfile.source);
|
||||||
show_diff(&full_source, &dotfile.target);
|
if full_source.is_dir() {
|
||||||
|
// Show diffs for individual conflicted files
|
||||||
|
let relevant: Vec<_> = file_conflicts
|
||||||
|
.iter()
|
||||||
|
.filter(|(file_path, _, _, _)| {
|
||||||
|
file_path.starts_with(&dotfile.target)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if relevant.is_empty() {
|
||||||
|
println!(" (no file-level diffs to show)");
|
||||||
|
} else {
|
||||||
|
for (file_path, src, tgt, _) in &relevant {
|
||||||
|
let relative = file_path
|
||||||
|
.strip_prefix(&dotfile.target)
|
||||||
|
.unwrap_or(file_path);
|
||||||
|
println!("\n diff: {}", relative.display());
|
||||||
|
show_diff(src, tgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print!(
|
||||||
|
" Use source for all {} conflicted file{}? [y/n]: ",
|
||||||
|
relevant.len(),
|
||||||
|
if relevant.len() == 1 { "" } else { "s" }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
show_diff(&full_source, &dotfile.target);
|
||||||
print!(" Use source? [y/n]: ");
|
print!(" Use source? [y/n]: ");
|
||||||
|
}
|
||||||
|
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
let mut confirm = String::new();
|
let mut confirm = String::new();
|
||||||
io::stdin().read_line(&mut confirm)?;
|
io::stdin().read_line(&mut confirm)?;
|
||||||
|
|
@ -312,7 +342,46 @@ pub fn run(config_path: Option<PathBuf>, dry_run: bool, prune: bool) -> anyhow::
|
||||||
}
|
}
|
||||||
"m" => {
|
"m" => {
|
||||||
let full_source = source_dir.join(&dotfile.source);
|
let full_source = source_dir.join(&dotfile.source);
|
||||||
if merge_in_editor(&full_source, &dotfile.target)? {
|
if full_source.is_dir() {
|
||||||
|
// Merge individual conflicted files
|
||||||
|
let relevant: Vec<_> = file_conflicts
|
||||||
|
.iter()
|
||||||
|
.filter(|(file_path, _, _, _)| {
|
||||||
|
file_path.starts_with(&dotfile.target)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if relevant.is_empty() {
|
||||||
|
println!(" (no file-level conflicts to merge)");
|
||||||
|
} else {
|
||||||
|
let mut all_merged = true;
|
||||||
|
for (file_path, src, tgt, _) in &relevant {
|
||||||
|
let relative = file_path
|
||||||
|
.strip_prefix(&dotfile.target)
|
||||||
|
.unwrap_or(file_path);
|
||||||
|
println!("\n Merging {}...", relative.display());
|
||||||
|
if !merge_in_editor(src, tgt)? {
|
||||||
|
println!(
|
||||||
|
" Merge cancelled for {}.",
|
||||||
|
relative.display()
|
||||||
|
);
|
||||||
|
all_merged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if all_merged {
|
||||||
|
deploy_set.insert(idx);
|
||||||
|
} else {
|
||||||
|
print!(
|
||||||
|
" Some files not merged. Deploy anyway? [y/n]: "
|
||||||
|
);
|
||||||
|
io::stdout().flush()?;
|
||||||
|
let mut confirm = String::new();
|
||||||
|
io::stdin().read_line(&mut confirm)?;
|
||||||
|
if confirm.trim().to_lowercase() == "y" {
|
||||||
|
deploy_set.insert(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if merge_in_editor(&full_source, &dotfile.target)? {
|
||||||
// Source was updated with merged content, deploy it
|
// Source was updated with merged content, deploy it
|
||||||
deploy_set.insert(idx);
|
deploy_set.insert(idx);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -330,6 +399,7 @@ pub fn run(config_path: Option<PathBuf>, dry_run: bool, prune: bool) -> anyhow::
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // else (not dry_run)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dry_prefix = if dry_run { "[dry-run] " } else { "" };
|
let dry_prefix = if dry_run { "[dry-run] " } else { "" };
|
||||||
|
|
@ -583,19 +653,33 @@ fn prompt_uninstall(package: &str) -> anyhow::Result<bool> {
|
||||||
fn show_diff(source: &PathBuf, target: &PathBuf) {
|
fn show_diff(source: &PathBuf, target: &PathBuf) {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
if source.is_file() && target.is_file() {
|
if !source.is_file() {
|
||||||
let output = Command::new("diff")
|
println!(" (source does not exist: {})", source.display());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !target.is_file() {
|
||||||
|
println!(" (target does not exist: {})", target.display());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match Command::new("diff")
|
||||||
.arg("--color=always")
|
.arg("--color=always")
|
||||||
.arg("-u")
|
.arg("-u")
|
||||||
.arg(target)
|
.arg(target)
|
||||||
.arg(source)
|
.arg(source)
|
||||||
.output();
|
.output()
|
||||||
|
{
|
||||||
if let Ok(output) = output {
|
Ok(output) => {
|
||||||
println!("{}", String::from_utf8_lossy(&output.stdout));
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
}
|
if stdout.trim().is_empty() {
|
||||||
|
println!(" (no content differences)");
|
||||||
} else {
|
} else {
|
||||||
println!(" (diff not available for directories)");
|
println!("{}", stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(" (failed to run diff: {})", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,10 @@ impl StateStore {
|
||||||
(false, false) => SyncStatus::Synced,
|
(false, false) => SyncStatus::Synced,
|
||||||
(true, false) => SyncStatus::SourceChanged,
|
(true, false) => SyncStatus::SourceChanged,
|
||||||
(false, true) => SyncStatus::TargetChanged,
|
(false, true) => SyncStatus::TargetChanged,
|
||||||
|
(true, true) if current_source_hash == current_target_hash => {
|
||||||
|
// Both changed but converged to the same content — not a real conflict
|
||||||
|
SyncStatus::SourceChanged
|
||||||
|
}
|
||||||
(true, true) => SyncStatus::Conflict,
|
(true, true) => SyncStatus::Conflict,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue