<commit_before>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#[macro_use]
extern crate clap;
extern crate git2;

use git2::{Error, ObjectType, Repository, Commit, Oid};
use clap::App;
use std::collections::HashMap;

fn main() {
    let yaml = load_yaml!("cli.yaml");
    let matches = App::from_yaml(yaml).get_matches();

    let path = matches.value_of("path").expect("Could not parse 'path' parameter");
    let revision_range = matches.value_of("revision_range").expect("Could not parse 'revision range' parameter");

    let repo = Repository::open(path).expect("Could not open repository");
    let tags = get_tags(&repo).expect("Could not retrieve tags from repo");
    parse_log(&repo, revision_range, &tags).expect("Could not parse log");
}

type TagHashMap = HashMap<Oid, String>;
fn get_tags(repo: &Repository) -> Result<TagHashMap, Error> {
    let mut tags = HashMap::new();
    for name in try!(repo.tag_names(None)).iter() {
        let name = name.expect("Could not retrieve tag name");
        let obj = try!(repo.revparse_single(name));
        if let Ok(tag) = obj.into_tag() {
            let tag_name = tag.name().expect("Could not parse tag name").to_owned();
            tags.insert(tag.target_id(), tag_name);
        }
    }
    Ok(tags)
}

fn parse_log(repo: &Repository, revision_range: &str, tags: &TagHashMap) -> Result<(), Error> {
    let mut revwalk = try!(repo.revwalk());
    revwalk.set_sorting(git2::SORT_TIME);

    // Fill the revwalk with the selected revisions.
    let revspec = try!(repo.revparse(&revision_range));
    if revspec.mode().contains(git2::REVPARSE_SINGLE) {
        try!(revwalk.push(revspec.from().unwrap().id()));
    } else {
        let from = revspec.from().unwrap().id();
        let to = revspec.to().unwrap().id();
        try!(revwalk.push(to));
        if revspec.mode().contains(git2::REVPARSE_MERGE_BASE) {
            let base = try!(repo.merge_base(from, to));
            let o = try!(repo.find_object(base, Some(ObjectType::Commit)));
            try!(revwalk.push(o.id()));
        }
        try!(revwalk.hide(from));
    }

    // Iterate over the git objects and process them.
    for id in revwalk {
        let oid = try!(id);
        let commit = try!(repo.find_commit(oid));
        if let Some(tag) = tags.get(&oid) {
            println!("Found tag {} for commit {}", tag, oid);
        }
        print_commit(commit);
    }
    Ok(())
}

fn print_commit(commit: Commit) {
    println!("Commit: {}", commit.id());

    if commit.parents().len() > 1 {
        print!("Merge:");
        for id in commit.parent_ids() {
            print!(" {:.8}", id);
        }
        println!("");
    }

    let author = commit.author();
    println!("Author: {}\n", author);

    for line in String::from_utf8_lossy(commit.message_bytes()).lines() {
        println!("\t{}", line);
    }
    println!("\n");
}
<commit_msg>Changed structure to use a dedicated GitJournal object<commit_after>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#[macro_use]
extern crate clap;
extern crate git2;

use git2::{Error, ObjectType, Repository, Commit, Oid};
use clap::App;

struct GitJournal {
    repo: Repository,
    tags: Vec<(Oid, String)>,
}

impl GitJournal {
    fn new(path: &str) -> Result<GitJournal, Error> {
        // Open the repository
        let new_repo = try!(Repository::open(path));

        // Get all available tags
        let mut new_tags = vec![];
        for name in try!(new_repo.tag_names(None)).iter() {
            let name = try!(name.ok_or(Error::from_str("Could not receive tag name")));
            let obj = try!(new_repo.revparse_single(name));
            if let Ok(tag) = obj.into_tag() {
                let tag_name = try!(tag.name().ok_or(Error::from_str("Could not parse tag name"))).to_owned();
                new_tags.push((tag.target_id(), tag_name));
            }
        }
        Ok(GitJournal {
            repo: new_repo,
            tags: new_tags,
        })
    }

    fn parse_log(&self, revision_range: &str) -> Result<(), Error> {
        let mut revwalk = try!(self.repo.revwalk());
        revwalk.set_sorting(git2::SORT_TIME);

        // Fill the revwalk with the selected revisions.
        let revspec = try!(self.repo.revparse(&revision_range));
        if revspec.mode().contains(git2::REVPARSE_SINGLE) {
            try!(revwalk.push(revspec.from().unwrap().id()));
        } else {
            let from = revspec.from().unwrap().id();
            let to = revspec.to().unwrap().id();
            try!(revwalk.push(to));
            if revspec.mode().contains(git2::REVPARSE_MERGE_BASE) {
                let base = try!(self.repo.merge_base(from, to));
                let o = try!(self.repo.find_object(base, Some(ObjectType::Commit)));
                try!(revwalk.push(o.id()));
            }
            try!(revwalk.hide(from));
        }

        // Iterate over the git objects and process them.
        for id in revwalk {
            let oid = try!(id);
            let commit = try!(self.repo.find_commit(oid));
            for &(tag_id, ref tag_name) in self.tags.iter().filter(|tag| tag.0.as_bytes() == oid.as_bytes()) {
                println!("Found tag {} for commit {}.", tag_name, tag_id);
            }
            self.print_commit(commit);
        }
        Ok(())
    }

    fn print_commit(&self, commit: Commit) {
        println!("Commit: {}", commit.id());

        if commit.parents().len() > 1 {
            print!("Merge:");
            for id in commit.parent_ids() {
                print!(" {:.8}", id);
            }
            println!("");
        }

        let author = commit.author();
        println!("Author: {}\n", author);

        for line in String::from_utf8_lossy(commit.message_bytes()).lines() {
            println!("\t{}", line);
        }
        println!("\n");
    }
}

fn main() {
    let yaml = load_yaml!("cli.yaml");
    let matches = App::from_yaml(yaml).get_matches();

    let path = matches.value_of("path").expect("Could not parse 'path' parameter");
    let revision_range = matches.value_of("revision_range").expect("Could not parse 'revision range' parameter");

    match GitJournal::new(path) {
        Ok(journal) => journal.parse_log(revision_range).expect("Log parsing error"),
        Err(e) => println!("{}", e),
    }
}

{'fraction_non_alphanumeric': 0.16703022748519789, 'fraction_numerical': 0.002648800249298847, 'mean_word_length': 2.6001121704991585, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '855083', 'n_tokens_mistral': 2024, 'n_tokens_neox': 2005, 'n_words': 439}