-
-
Notifications
You must be signed in to change notification settings - Fork 102
/
git.rs
108 lines (89 loc) · 2.88 KB
/
git.rs
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
use std::{
io::Result,
path::{Path, PathBuf},
process::{Command, Output},
};
fn git_command(path: impl AsRef<Path>, subcommand: &[&str]) -> Result<Output> {
let git_path = path
.as_ref()
.to_str()
.expect("Repository path must be a valid UTF-8 string");
let mut args = vec!["-C", git_path];
args.extend(subcommand);
Command::new("git").args(&args).output()
}
pub fn repo_hash(path: impl AsRef<Path>) -> Option<String> {
let output = git_command(path.as_ref(), &["rev-parse", "--short=12", "HEAD"]).ok()?;
if !output.status.success() {
return None;
}
let hash = String::from_utf8(output.stdout).ok()?;
let hash = hash.trim_end_matches('\n');
if dirty(path) {
Some(format!("{hash}+"))
} else {
Some(hash.into())
}
}
fn dirty(path: impl AsRef<Path>) -> bool {
match git_command(path.as_ref(), &["ls-files", "-m"]) {
Ok(modified_files) => !modified_files.stdout.is_empty(),
Err(_) => false,
}
}
fn gitmodules_config(subcommand: &[&str]) -> Option<String> {
let mut args = vec!["config", "-f", ".gitmodules", "-z"];
args.extend(subcommand);
let output = git_command(Path::new("."), &args).ok()?;
if !output.status.success() {
return None;
}
let mut result = String::from_utf8(output.stdout).ok()?;
assert_eq!(result.pop(), Some('\0'));
Some(result)
}
fn path_command(path: impl AsRef<Path>, subcommand: &[&str]) -> Option<PathBuf> {
let mut output = git_command(path, subcommand).ok()?;
if !output.status.success() {
return None;
}
assert_eq!(
output
.stdout
.pop()
.map(u32::from)
.and_then(std::char::from_u32),
Some('\n')
);
Some(path_from_output(output.stdout))
}
#[cfg(unix)]
fn path_from_output(output: Vec<u8>) -> PathBuf {
use std::{ffi::OsString, os::unix::prelude::OsStringExt};
OsString::from_vec(output).into()
}
#[cfg(not(unix))]
fn path_from_output(output: Vec<u8>) -> PathBuf {
String::from_utf8(output).unwrap().into()
}
pub fn toplevel(path: impl AsRef<Path>) -> Option<PathBuf> {
path_command(path, &["rev-parse", "--show-toplevel"])
}
// Only build.rs uses this
#[allow(dead_code)]
pub fn git_dir(path: impl AsRef<Path>) -> Option<PathBuf> {
path_command(path, &["rev-parse", "--git-dir"])
}
pub(crate) fn repo_remote_url(path: impl AsRef<Path>) -> Option<String> {
// Find the subsection that defines the module for the given path:
let key_for_path = gitmodules_config(&[
"--name-only",
"--get-regexp",
r"submodule\..+\.path",
&format!("^{}$", path.as_ref().display()),
])?;
let subsection = key_for_path
.strip_suffix(".path")
.expect("submodule.<subsection>.path should end with '.path'");
gitmodules_config(&["--get", &format!("{subsection}.url")])
}