1use std::io::{self, Write as _};
5use std::path::PathBuf;
6use std::sync::atomic::AtomicBool;
7
8use clap::Parser;
9use girt::config::ConfigInputs;
10use girt::content_diff::DiffLimits;
11use girt::patch::{Limits, Options};
12use girt::revision::Resolver;
13use girt::tree::TreeEditor;
14use girt::workflow::Environment;
15use girt::{
16 InitKind, ObjectFormat, ObjectKind, PackLimits, ReadLimits, Repo, Repository, rewrites,
17};
18
19#[derive(Parser)]
20#[command(about, long_about = None)]
21struct Args {
22 path: Option<PathBuf>,
24 #[arg(default_value = "HEAD~1")]
26 old: String,
27 #[arg(default_value = "HEAD")]
29 new: String,
30 #[arg(short = 'U', long, default_value_t = 3)]
32 unified: usize,
33 #[arg(long)]
35 trusted: bool,
36 #[arg(long)]
38 no_indent_heuristic: bool,
39}
40
41fn main() -> Result<(), Box<dyn std::error::Error>> {
42 let args = Args::parse();
43 let temp = tempfile::tempdir()?;
44 let mut out = io::BufWriter::new(io::stdout().lock());
45
46 let Some(path) = args.path else {
47 let repo = Repository::init(ObjectFormat::Sha1, temp.path().join("r"), InitKind::Bare)?;
49 let loose = repo.loose_objects();
50 let mut editor = TreeEditor::new(&loose);
51 editor.set(b"notes.txt", loose.write_blob(b"one\ntwo\nthree\n")?)?;
52 editor.set(b"old name", loose.write_blob(b"moved\ncontent\n")?)?;
53 let old = editor.write()?;
54
55 editor.set(b"notes.txt", loose.write_blob(b"one\n2\nthree")?)?;
56 editor.remove(b"old name")?;
57 editor.set(b"new name", loose.write_blob(b"moved\ncontent\n")?)?;
58 let new = editor.write()?;
59
60 let git = Repo::new(repo);
61 git.write_patch(&mut out, old, new)?;
62 out.flush()?;
63 return Ok(());
64 };
65
66 if args.unified == 3 && !args.trusted && !args.no_indent_heuristic {
67 Repo::discover(path)?.write_patch(&mut out, &args.old, &args.new)?;
68 out.flush()?;
69 return Ok(());
70 }
71
72 let environment = Environment::from_process();
74 let repo = Repository::discover_with_config(path, &ConfigInputs::git_default(&environment)?)?;
75
76 let (packs, rewrite_limits, limits) = if args.trusted {
77 let diff = DiffLimits::default()
78 .with_max_input_bytes(usize::MAX)
79 .with_max_lines(usize::MAX)
80 .with_max_trace(usize::MAX)
81 .with_max_work(usize::MAX);
82
83 let rewrites = rewrites::Limits::default()
85 .with_read(ReadLimits::trusted())
86 .with_max_blob_bytes(usize::MAX)
87 .with_max_spans(usize::MAX)
88 .with_max_work(usize::MAX);
89
90 (
91 PackLimits::trusted(),
92 rewrites,
93 Limits::default()
94 .with_read(ReadLimits::trusted())
95 .with_diff(diff),
96 )
97 } else {
98 (
99 PackLimits::default(),
100 rewrites::Limits::default(),
101 Limits::default(),
102 )
103 };
104
105 let objects = repo.objects_with(packs)?;
106 let resolver = Resolver::new(&repo, &objects)?;
107 let old = resolver.resolve_to(&args.old.parse()?, ObjectKind::Tree)?;
108 let new = resolver.resolve_to(&args.new.parse()?, ObjectKind::Tree)?;
109
110 let cancel = AtomicBool::new(false);
111 let comparison = objects.diff_trees_with(
112 Some(old),
113 Some(new),
114 rewrites::Options::default().with_track_empty(true),
115 rewrite_limits,
116 &cancel,
117 )?;
118
119 let mut attributes = repo.diff_attributes(&environment)?;
121 objects.write_patch_with(
122 &mut out,
123 &comparison.entries,
124 Options::default()
125 .with_context_lines(args.unified)
126 .with_indent_heuristic(!args.no_indent_heuristic),
127 Some(&mut attributes),
128 limits,
129 &cancel,
130 )?;
131
132 out.flush()?;
133 Ok(())
134}