1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// This file is part of radicle-surf
// <https://github.com/radicle-dev/radicle-surf>
//
// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 or
// later as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

extern crate radicle_surf;

use std::{env::Args, str::FromStr, time::Instant};

use radicle_git_ext::Oid;
use radicle_surf::{diff::Diff, Repository};

fn main() {
    let options = get_options_or_exit();
    let repo = init_repository_or_exit(&options.path_to_repo);
    let head_oid = match options.head_revision {
        HeadRevision::Head => repo.head().unwrap(),
        HeadRevision::Commit(id) => Oid::from_str(&id).unwrap(),
    };
    let base_oid = Oid::from_str(&options.base_revision).unwrap();
    let now = Instant::now();
    let elapsed_nanos = now.elapsed().as_nanos();
    let diff = repo.diff(base_oid, head_oid).unwrap();
    print_diff_summary(&diff, elapsed_nanos);
}

fn get_options_or_exit() -> Options {
    match Options::parse(std::env::args()) {
        Ok(options) => options,
        Err(message) => {
            println!("{message}");
            std::process::exit(1);
        }
    }
}

fn init_repository_or_exit(path_to_repo: &str) -> Repository {
    match Repository::open(path_to_repo) {
        Ok(repo) => repo,
        Err(e) => {
            println!("Failed to create repository: {e:?}");
            std::process::exit(1);
        }
    }
}

fn print_diff_summary(diff: &Diff, elapsed_nanos: u128) {
    diff.added().for_each(|created| {
        println!("+++ {:?}", created.path);
    });
    diff.deleted().for_each(|deleted| {
        println!("--- {:?}", deleted.path);
    });
    diff.modified().for_each(|modified| {
        println!("mod {:?}", modified.path);
    });

    println!(
        "created {} / deleted {} / modified {} / total {}",
        diff.added().count(),
        diff.deleted().count(),
        diff.modified().count(),
        diff.added().count() + diff.deleted().count() + diff.modified().count()
    );
    println!("diff took {elapsed_nanos} nanos ");
}

struct Options {
    path_to_repo: String,
    base_revision: String,
    head_revision: HeadRevision,
}

enum HeadRevision {
    Head,
    Commit(String),
}

impl Options {
    fn parse(args: Args) -> Result<Self, String> {
        let args: Vec<String> = args.collect();
        if args.len() != 4 {
            return Err(format!(
                "Usage: {} <path-to-repo> <base-revision> <head-revision>\n\
                \tpath-to-repo: Path to the directory containing .git subdirectory\n\
                \tbase-revision: Git commit ID of the base revision (one that will be considered less recent)\n\
                \thead-revision: Git commit ID of the head revision (one that will be considered more recent) or 'HEAD' to use current git HEAD\n",
                args[0]));
        }

        let path_to_repo = args[1].clone();
        let base_revision = args[2].clone();
        let head_revision = {
            if args[3].eq_ignore_ascii_case("HEAD") {
                HeadRevision::Head
            } else {
                HeadRevision::Commit(args[3].clone())
            }
        };

        Ok(Options {
            path_to_repo,
            base_revision,
            head_revision,
        })
    }
}