1use super::Result;
6use std::iter::FromIterator;
7use std::process::{Command, Output};
8
9pub fn in_git_repository() -> Result<bool> {
11 git(&["rev-parse", "--is-inside-work-tree"]).map(|o| o.status.success())
12}
13
14pub fn last_tag() -> Result<Option<String>> {
16 last_tags(1).map(|mut v| v.pop())
17}
18
19pub 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
26pub 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
36pub 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
43fn 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
55fn 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
66fn 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
77fn 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 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}