diff/
diff.rs

1// This file is part of radicle-surf
2// <https://github.com/radicle-dev/radicle-surf>
3//
4// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3 or
8// later as published by the Free Software Foundation.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18extern crate radicle_surf;
19
20use std::{env::Args, str::FromStr, time::Instant};
21
22use radicle_git_ext::Oid;
23use radicle_surf::{diff::Diff, Repository};
24
25fn main() {
26    let options = get_options_or_exit();
27    let repo = init_repository_or_exit(&options.path_to_repo);
28    let head_oid = match options.head_revision {
29        HeadRevision::Head => repo.head().unwrap(),
30        HeadRevision::Commit(id) => Oid::from_str(&id).unwrap(),
31    };
32    let base_oid = Oid::from_str(&options.base_revision).unwrap();
33    let now = Instant::now();
34    let elapsed_nanos = now.elapsed().as_nanos();
35    let diff = repo.diff(base_oid, head_oid).unwrap();
36    print_diff_summary(&diff, elapsed_nanos);
37}
38
39fn get_options_or_exit() -> Options {
40    match Options::parse(std::env::args()) {
41        Ok(options) => options,
42        Err(message) => {
43            println!("{message}");
44            std::process::exit(1);
45        }
46    }
47}
48
49fn init_repository_or_exit(path_to_repo: &str) -> Repository {
50    match Repository::open(path_to_repo) {
51        Ok(repo) => repo,
52        Err(e) => {
53            println!("Failed to create repository: {e:?}");
54            std::process::exit(1);
55        }
56    }
57}
58
59fn print_diff_summary(diff: &Diff, elapsed_nanos: u128) {
60    diff.added().for_each(|created| {
61        println!("+++ {:?}", created.path);
62    });
63    diff.deleted().for_each(|deleted| {
64        println!("--- {:?}", deleted.path);
65    });
66    diff.modified().for_each(|modified| {
67        println!("mod {:?}", modified.path);
68    });
69
70    println!(
71        "created {} / deleted {} / modified {} / total {}",
72        diff.added().count(),
73        diff.deleted().count(),
74        diff.modified().count(),
75        diff.added().count() + diff.deleted().count() + diff.modified().count()
76    );
77    println!("diff took {elapsed_nanos} nanos ");
78}
79
80struct Options {
81    path_to_repo: String,
82    base_revision: String,
83    head_revision: HeadRevision,
84}
85
86enum HeadRevision {
87    Head,
88    Commit(String),
89}
90
91impl Options {
92    fn parse(args: Args) -> Result<Self, String> {
93        let args: Vec<String> = args.collect();
94        if args.len() != 4 {
95            return Err(format!(
96                "Usage: {} <path-to-repo> <base-revision> <head-revision>\n\
97                \tpath-to-repo: Path to the directory containing .git subdirectory\n\
98                \tbase-revision: Git commit ID of the base revision (one that will be considered less recent)\n\
99                \thead-revision: Git commit ID of the head revision (one that will be considered more recent) or 'HEAD' to use current git HEAD\n",
100                args[0]));
101        }
102
103        let path_to_repo = args[1].clone();
104        let base_revision = args[2].clone();
105        let head_revision = {
106            if args[3].eq_ignore_ascii_case("HEAD") {
107                HeadRevision::Head
108            } else {
109                HeadRevision::Commit(args[3].clone())
110            }
111        };
112
113        Ok(Options {
114            path_to_repo,
115            base_revision,
116            head_revision,
117        })
118    }
119}