choir_revert_scan/
choir-revert-scan.rs1use choir_merge::silent_revert::{scan_repo, ScanReport};
24
25fn main() {
26 let args: Vec<String> = std::env::args().skip(1).collect();
27 let mut max = 0usize;
28 let mut quiet = false;
29 let mut repos: Vec<String> = Vec::new();
30 let mut it = args.iter();
31 while let Some(arg) = it.next() {
32 match arg.as_str() {
33 "--max" => match it.next().and_then(|n| n.parse().ok()) {
34 Some(n) => max = n,
35 None => fail("--max wants a number"),
36 },
37 "--quiet" => quiet = true,
38 other if other.starts_with("--") => fail(&format!("unknown flag {other}")),
39 other => repos.push(other.to_string()),
40 }
41 }
42 if repos.is_empty() {
43 fail("usage: choir-revert-scan [--max N] [--quiet] <repo>...");
44 }
45
46 let mut total = ScanReport::default();
47 for repo in &repos {
48 match scan_repo(std::path::Path::new(repo), max) {
49 Ok(report) => {
50 println!(
51 "{repo}: {} merges scanned of {} seen, {} findings across {} paths",
52 report.merges_scanned,
53 report.merges_seen,
54 report.findings.len(),
55 report.paths_scanned
56 );
57 total.absorb(report);
58 }
59 Err(e) => eprintln!("{repo}: skipped, {e}"),
63 }
64 }
65
66 if !quiet {
67 for f in &total.findings {
68 println!("\n{} {}", &f.merge[..f.merge.len().min(12)], f.path);
69 for line in &f.reverted {
70 println!(" reverted: {line}");
71 }
72 for line in &f.injected {
73 println!(" injected: {line}");
74 }
75 }
76 }
77
78 println!("\n repositories {}", repos.len());
79 println!(" merges seen {}", total.merges_seen);
80 println!(" merges scanned {}", total.merges_scanned);
81 println!(" paths scanned {}", total.paths_scanned);
82 println!(" skipped octopus {}", total.skipped_octopus);
83 println!(" skipped no base {}", total.skipped_no_base);
84 println!(" skipped binary {}", total.skipped_binary);
85 println!(" skipped large {}", total.skipped_large);
86 println!(" lines relocated {}", total.lines_relocated);
87 println!(" paths all moved {}", total.paths_all_relocated);
88 println!(" lines surviving {}", total.lines_surviving_elsewhere);
89 println!(" findings {}", total.findings.len());
90 println!(" of those, surviving {}", total.findings_all_surviving);
91 println!(" merges violating {:.4}", total.merge_violation_rate());
92 println!(
95 "\n A finding is evidence, not proof: a merge whose conflicts a\n \
96 human resolved by hand may legitimately drop lines neither\n \
97 parent dropped. \"Surviving\" counts reverted lines still\n \
98 present somewhere in the result, which is what a refactor that\n \
99 moves code looks like; they are counted and still reported,\n \
100 because presence is a cheap test a short line passes by\n \
101 accident. The true rate is between the two.\n \
102 Look at the findings before quoting either number."
103 );
104}
105
106fn fail(msg: &str) -> ! {
108 eprintln!("choir-revert-scan: {msg}");
109 std::process::exit(2);
110}