changelog/
git.rs

1// Copyright 2017-2018 by Aldrin J D'Souza.
2// Licensed under the MIT License <https://opensource.org/licenses/MIT>
3
4// All git interactions
5use super::Result;
6use std::iter::FromIterator;
7use std::process::{Command, Output};
8
9/// Check if we're in an git repository?
10pub fn in_git_repository() -> Result<bool> {
11    git(&["rev-parse", "--is-inside-work-tree"]).map(|o| o.status.success())
12}
13
14/// Get the last tag
15pub fn last_tag() -> Result<Option<String>> {
16    last_tags(1).map(|mut v| v.pop())
17}
18
19/// Get the SHAs for all commits returned by `git log <args>`
20pub fn commits_in_log(args: &[String]) -> Result<Vec<String>> {
21    let mut log_args = vec!["log", "--format=format:%H"];
22    log_args.extend(args.iter().map(String::as_str));
23    git(&log_args).map(|o| read_lines(&o))
24}
25
26/// Get the commit message for the given sha
27pub fn get_commit_message(sha: &str) -> Result<Vec<String>> {
28    git(&[
29        "log",
30        "--format=format:%H%n%an%n%aD%n%s%n%b",
31        "--max-count=1",
32        sha,
33    ]).map(|o| read_lines(&o))
34}
35
36/// Get the fetch url for the given origin
37pub fn get_remote_url(name: &str) -> Result<Option<String>> {
38    git(&["remote", "get-url", name])
39        .map(|o| read_lines(&o))
40        .map(|mut v: Vec<String>| v.pop().and_then(usable_url))
41}
42
43/// Check if the remote URL is usable for links
44fn usable_url(raw: String) -> Option<String> {
45    if raw.to_lowercase().starts_with("http") {
46        if let Some(index) = raw.rfind(".git") {
47            return Some(raw[0..index].to_string());
48        } else {
49            return Some(raw);
50        }
51    }
52    None
53}
54
55/// Get the last n tags
56fn last_tags(n: i32) -> Result<Vec<String>> {
57    git(&[
58        "for-each-ref",
59        &format!("--count={}", n),
60        "--sort=-taggerdate",
61        "--format=%(refname:short)",
62        "refs/tags/*",
63    ]).map(|o| read_lines(&o))
64}
65
66/// Invoke a git command with the given arguments.
67fn git(args: &[&str]) -> Result<Output> {
68    trace!("git {}", args.join(" "));
69    let output = Command::new("git").args(args).output()?;
70    if output.status.success() {
71        Ok(output)
72    } else {
73        Err(format_err!("{}", String::from_utf8_lossy(&output.stderr)))
74    }
75}
76
77/// Read the lines from the output and gather them into a String collection.
78fn read_lines<T: FromIterator<String>>(o: &Output) -> T {
79    String::from_utf8_lossy(&o.stdout)
80        .lines()
81        .map(String::from)
82        .collect::<T>()
83}
84
85#[cfg(test)]
86mod tests {
87    #[test]
88    fn in_git_repository() {
89        assert!(super::in_git_repository().unwrap());
90    }
91
92    #[test]
93    fn last_tag() {
94        assert!(super::last_tag().is_ok());
95    }
96
97    #[test]
98    fn commits_in_log() {
99        use super::commits_in_log;
100        let mut range = vec![String::from("v0.1.1..v0.2.0")];
101        let commits = commits_in_log(&range);
102        assert!(commits.is_ok(), "{:?}", commits);
103        let forward = commits.unwrap();
104        assert_eq!(forward.len(), 2);
105
106        // Add a `git log` option
107        range.push(String::from("--reverse"));
108        let commits = commits_in_log(&range);
109        assert!(commits.is_ok(), "{:?}", commits);
110        let mut backward = commits.unwrap();
111        backward.reverse();
112        assert_eq!(backward, forward);
113    }
114
115    #[test]
116    fn get_commit_message() {
117        use super::get_commit_message;
118        assert!(get_commit_message("v0.1.1").is_ok());
119        assert!(get_commit_message("bad").is_err());
120    }
121
122    #[test]
123    fn get_usable_url() {
124        use super::usable_url;
125        let ssh = String::from("git@github.com:aldrin/git-changelog.git");
126        let raw = String::from("https://github.com/aldrin/git-changelog.git");
127        let usable = "https://github.com/aldrin/git-changelog";
128        assert_eq!(usable_url(usable.to_string()), Some(usable.to_string()));
129        assert_eq!(usable_url(raw), Some(usable.to_string()));
130        assert_eq!(usable_url(ssh), None);
131    }
132
133    #[test]
134    fn get_remote_url() {
135        use super::get_remote_url;
136        let expected = Some(String::from("https://github.com/aldrin/git-changelog"));
137        let found = get_remote_url("origin").unwrap();
138        assert!(get_remote_url("bad").is_err());
139        assert_eq!(found, expected);
140    }
141}