use std::path::Path;
use subversion::{
client::{CheckoutOptions, Context},
conflict::{ConflictChoice, ConflictDescription, ConflictResolver, ConflictResult},
merge::{merge_peg, MergeOptions},
Depth, Error, Revision,
};
struct FixedChoiceResolver(ConflictChoice);
impl ConflictResolver for FixedChoiceResolver {
fn resolve(
&mut self,
_conflict: &ConflictDescription,
) -> Result<ConflictResult, Error<'static>> {
Ok(ConflictResult {
choice: self.0,
merged_file: None,
save_merged: false,
})
}
}
struct SmartConflictResolver {
log_file: std::fs::File,
}
impl SmartConflictResolver {
fn new() -> std::io::Result<Self> {
Ok(Self {
log_file: std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("conflict_log.txt")?,
})
}
}
impl ConflictResolver for SmartConflictResolver {
fn resolve(
&mut self,
conflict: &ConflictDescription,
) -> Result<ConflictResult, Error<'static>> {
use std::io::Write;
writeln!(
self.log_file,
"Conflict in {}: {:?} (action: {:?}, reason: {:?})",
conflict.local_abspath, conflict.kind, conflict.action, conflict.reason
)
.unwrap();
let choice = if conflict.is_binary {
ConflictChoice::TheirsFull
} else if let Some(ref mime) = conflict.mime_type {
if mime.starts_with("text/") {
ConflictChoice::TheirsConflict
} else {
ConflictChoice::Postpone
}
} else if conflict.local_abspath.ends_with(".generated") {
ConflictChoice::TheirsFull
} else {
ConflictChoice::Postpone
};
writeln!(self.log_file, " Resolution: {:?}", choice).unwrap();
Ok(ConflictResult {
choice,
merged_file: None,
save_merged: false,
})
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!(
"Usage: {} <repository_url> <working_copy_path> [--theirs|--mine|--smart]",
args[0]
);
std::process::exit(1);
}
let repo_url = &args[1];
let wc_path = Path::new(&args[2]);
let resolver_type = args.get(3).map(|s| s.as_str()).unwrap_or("--postpone");
let mut ctx = Context::new()?;
match resolver_type {
"--theirs" => {
println!("Using automatic resolver: always choose theirs");
ctx.set_conflict_resolver(FixedChoiceResolver(ConflictChoice::TheirsFull));
}
"--mine" => {
println!("Using automatic resolver: always choose mine");
ctx.set_conflict_resolver(FixedChoiceResolver(ConflictChoice::MineFull));
}
"--smart" => {
println!("Using smart conflict resolver");
ctx.set_conflict_resolver(SmartConflictResolver::new()?);
}
_ => {
println!("Using default resolver: postpone all conflicts");
ctx.set_conflict_resolver(FixedChoiceResolver(ConflictChoice::Postpone));
}
}
if !wc_path.exists() {
println!("Checking out {} to {:?}", repo_url, wc_path);
ctx.checkout(repo_url.as_str(), wc_path, &CheckoutOptions::default())?;
}
println!("Performing merge...");
let merge_options = MergeOptions {
dry_run: false,
record_only: false,
force_delete: false,
allow_mixed_rev: true,
..Default::default()
};
match merge_peg(
repo_url.as_str(),
None, Revision::Head,
wc_path,
Depth::Infinity,
&merge_options,
&mut ctx,
) {
Ok(()) => {
println!("Merge completed successfully!");
}
Err(e) => {
eprintln!("Merge failed: {}", e);
return Err(Box::new(e));
}
}
println!("\nChecking working copy status...");
println!("(Status checking would iterate through working copy)");
println!("\nMerge operation complete!");
if resolver_type == "--smart" {
println!("Check conflict_log.txt for details about resolved conflicts");
}
Ok(())
}