1 /*
2  * libgit2 "blame" example - shows how to use the blame API
3  *
4  * Written by the libgit2 contributors
5  *
6  * To the extent possible under law, the author(s) have dedicated all copyright
7  * and related and neighboring rights to this software to the public domain
8  * worldwide. This software is distributed without any warranty.
9  *
10  * You should have received a copy of the CC0 Public Domain Dedication along
11  * with this software. If not, see
12  * <http://creativecommons.org/publicdomain/zero/1.0/>.
13  */
14 
15 #![deny(warnings)]
16 
17 use git2::{BlameOptions, Repository};
18 use std::io::{BufRead, BufReader};
19 use std::path::Path;
20 use structopt::StructOpt;
21 
22 #[derive(StructOpt)]
23 #[allow(non_snake_case)]
24 struct Args {
25     #[structopt(name = "path")]
26     arg_path: String,
27     #[structopt(name = "spec")]
28     arg_spec: Option<String>,
29     #[structopt(short = "M")]
30     /// find line moves within and across files
31     flag_M: bool,
32     #[structopt(short = "C")]
33     /// find line copies within and across files
34     flag_C: bool,
35     #[structopt(short = "F")]
36     /// follow only the first parent commits
37     flag_F: bool,
38 }
39 
run(args: &Args) -> Result<(), git2::Error>40 fn run(args: &Args) -> Result<(), git2::Error> {
41     let repo = Repository::open(".")?;
42     let path = Path::new(&args.arg_path[..]);
43 
44     // Prepare our blame options
45     let mut opts = BlameOptions::new();
46     opts.track_copies_same_commit_moves(args.flag_M)
47         .track_copies_same_commit_copies(args.flag_C)
48         .first_parent(args.flag_F);
49 
50     let mut commit_id = "HEAD".to_string();
51 
52     // Parse spec
53     if let Some(spec) = args.arg_spec.as_ref() {
54         let revspec = repo.revparse(spec)?;
55 
56         let (oldest, newest) = if revspec.mode().contains(git2::RevparseMode::SINGLE) {
57             (None, revspec.from())
58         } else if revspec.mode().contains(git2::RevparseMode::RANGE) {
59             (revspec.from(), revspec.to())
60         } else {
61             (None, None)
62         };
63 
64         if let Some(commit) = oldest {
65             opts.oldest_commit(commit.id());
66         }
67 
68         if let Some(commit) = newest {
69             opts.newest_commit(commit.id());
70             if !commit.id().is_zero() {
71                 commit_id = format!("{}", commit.id())
72             }
73         }
74     }
75 
76     let spec = format!("{}:{}", commit_id, path.display());
77     let blame = repo.blame_file(path, Some(&mut opts))?;
78     let object = repo.revparse_single(&spec[..])?;
79     let blob = repo.find_blob(object.id())?;
80     let reader = BufReader::new(blob.content());
81 
82     for (i, line) in reader.lines().enumerate() {
83         if let (Ok(line), Some(hunk)) = (line, blame.get_line(i + 1)) {
84             let sig = hunk.final_signature();
85             println!(
86                 "{} {} <{}> {}",
87                 hunk.final_commit_id(),
88                 String::from_utf8_lossy(sig.name_bytes()),
89                 String::from_utf8_lossy(sig.email_bytes()),
90                 line
91             );
92         }
93     }
94 
95     Ok(())
96 }
97 
main()98 fn main() {
99     let args = Args::from_args();
100     match run(&args) {
101         Ok(()) => {}
102         Err(e) => println!("error: {}", e),
103     }
104 }
105