use std::collections::BTreeMap;
use std::io::Write;
use std::path::PathBuf;
use canonical_path::CanonicalPathBuf;
use clap::Clap;
use libpijul::change::*;
use libpijul::{MutTxnT, MutTxnTExt};
use serde_derive::Serialize;
use crate::repository::*;
#[derive(Clap, Debug)]
pub struct Diff {
#[clap(long = "repository")]
pub repo_path: Option<PathBuf>,
#[clap(long = "json")]
pub json: bool,
#[clap(long = "channel")]
pub channel: Option<String>,
#[clap(long = "tag")]
pub tag: bool,
#[clap(long = "short")]
pub short: bool,
pub prefixes: Vec<PathBuf>,
}
impl Diff {
pub fn run(mut self) -> Result<(), anyhow::Error> {
let mut repo = Repository::find_root(self.repo_path.clone())?;
let mut txn = repo.pristine.mut_txn_begin();
let mut stdout = std::io::stdout();
let mut channel =
txn.open_or_create_channel(repo.config.get_current_channel(self.channel.as_ref()))?;
let mut state = libpijul::RecordBuilder::new();
if self.prefixes.is_empty() {
txn.record(
&mut state,
libpijul::Algorithm::default(),
&mut channel,
&mut repo.working_copy,
&repo.changes,
"",
)?
} else {
self.fill_relative_prefixes()?;
repo.working_copy.record_prefixes(
&mut txn,
&mut channel,
&repo.changes,
&mut state,
CanonicalPathBuf::canonicalize(&repo.path)?,
&self.prefixes,
num_cpus::get(),
)?;
}
let rec = state.finish();
if rec.actions.is_empty() {
return Ok(());
}
let actions = rec
.actions
.into_iter()
.map(|rec| rec.globalize(&txn).unwrap())
.collect();
let mut change = LocalChange::make_change(
&txn,
&channel,
actions,
rec.contents,
ChangeHeader::default(),
Vec::new(),
)?;
let (dependencies, extra_known) = if self.tag {
full_dependencies(&txn, &channel)?
} else {
dependencies(&txn, &channel.borrow(), change.changes.iter())?
};
change.dependencies = dependencies;
change.extra_known = extra_known;
super::pager();
if self.json {
let mut changes = BTreeMap::new();
for ch in change.changes.iter() {
changes
.entry(ch.path())
.or_insert_with(Vec::new)
.push(Status {
operation: match ch {
Hunk::FileMove { .. } => "file move",
Hunk::FileDel { .. } => "file del",
Hunk::FileUndel { .. } => "file undel",
Hunk::SolveNameConflict { .. } => "solve name conflict",
Hunk::UnsolveNameConflict { .. } => "unsolve name conflict",
Hunk::FileAdd { .. } => "file add",
Hunk::Edit { .. } => "edit",
Hunk::Replacement { .. } => "replacement",
Hunk::SolveOrderConflict { .. } => "solve order conflict",
Hunk::UnsolveOrderConflict { .. } => "unsolve order conflict",
Hunk::ResurrectZombies { .. } => "resurrect zombies",
},
line: ch.line(),
});
}
serde_json::to_writer_pretty(&mut std::io::stdout(), &changes)?;
writeln!(stdout)?;
} else if self.short {
let mut changes = Vec::new();
for ch in change.changes.iter() {
changes.push(match ch {
Hunk::FileMove { path, .. } => format!("MV {}\n", path),
Hunk::FileDel { path, .. } => format!("D {}\n", path),
Hunk::FileUndel { path, .. } => format!("UD {}\n", path),
Hunk::FileAdd { path, .. } => format!("A {}", path),
Hunk::SolveNameConflict { path, .. } => format!("SC {}", path),
Hunk::UnsolveNameConflict { path, .. } => format!("UC {}", path),
Hunk::Edit {
local: Local { path, .. },
..
} => format!("M {}", path),
Hunk::Replacement {
local: Local { path, .. },
..
} => format!("R {}", path),
Hunk::SolveOrderConflict {
local: Local { path, .. },
..
} => format!("SC {}", path),
Hunk::UnsolveOrderConflict {
local: Local { path, .. },
..
} => format!("UC {}", path),
Hunk::ResurrectZombies {
local: Local { path, .. },
..
} => format!("RZ {}", path),
});
}
changes.sort_unstable();
changes.dedup();
for ch in changes {
println!("{}", ch);
}
} else {
change.write(
&repo.changes,
None,
|local: &libpijul::change::Local, _| -> String {
format!("{}:{}", local.path, local.line)
},
true,
&mut std::io::stdout(),
)?
}
Ok(())
}
fn fill_relative_prefixes(&mut self) -> Result<(), anyhow::Error> {
let cwd = std::env::current_dir()?;
for p in self.prefixes.iter_mut() {
if p.is_relative() {
*p = cwd.join(&p);
}
}
Ok(())
}
}
#[derive(Debug, Serialize)]
struct Status {
operation: &'static str,
line: Option<usize>,
}