fix(git): prevent core.fsmonitor from executing external commands (#3981)

This commit is contained in:
David Knaack
2023-07-31 21:44:31 +02:00
committed by GitHub
parent 53a8808e7a
commit 03278e4de4
3 changed files with 116 additions and 40 deletions
+61 -25
View File
@@ -4,9 +4,9 @@ use regex::Regex;
use super::{Context, Module, ModuleConfig};
use crate::configs::git_status::GitStatusConfig;
use crate::context;
use crate::formatter::StringFormatter;
use crate::segment::Segment;
use std::ffi::OsStr;
use std::sync::Arc;
const ALL_STATUS_FORMAT: &str =
@@ -31,10 +31,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_status");
let config: GitStatusConfig = GitStatusConfig::try_load(module.config);
let info = Arc::new(GitStatusInfo::load(context, config.clone()));
//Return None if not in git repository
context.get_repo().ok()?;
// Return None if not in git repository
let repo = context.get_repo().ok()?;
if let Some(git_status) = git_status_wsl(context, &config) {
if git_status.is_empty() {
@@ -44,6 +42,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return Some(module);
}
let info = Arc::new(GitStatusInfo::load(context, repo, config.clone()));
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
@@ -128,15 +128,21 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
struct GitStatusInfo<'a> {
context: &'a Context<'a>,
repo: &'a context::Repo,
config: GitStatusConfig<'a>,
repo_status: OnceCell<Option<RepoStatus>>,
stashed_count: OnceCell<Option<usize>>,
}
impl<'a> GitStatusInfo<'a> {
pub fn load(context: &'a Context, config: GitStatusConfig<'a>) -> Self {
pub fn load(
context: &'a Context,
repo: &'a context::Repo,
config: GitStatusConfig<'a>,
) -> Self {
Self {
context,
repo,
config,
repo_status: OnceCell::new(),
stashed_count: OnceCell::new(),
@@ -148,19 +154,20 @@ impl<'a> GitStatusInfo<'a> {
}
pub fn get_repo_status(&self) -> &Option<RepoStatus> {
self.repo_status
.get_or_init(|| match get_repo_status(self.context, &self.config) {
self.repo_status.get_or_init(|| {
match get_repo_status(self.context, self.repo, &self.config) {
Some(repo_status) => Some(repo_status),
None => {
log::debug!("get_repo_status: git status execution failed");
None
}
})
}
})
}
pub fn get_stashed(&self) -> &Option<usize> {
self.stashed_count
.get_or_init(|| match get_stashed_count(self.context) {
.get_or_init(|| match get_stashed_count(self.repo) {
Some(stashed_count) => Some(stashed_count),
None => {
log::debug!("get_stashed_count: git stash execution failed");
@@ -199,37 +206,35 @@ impl<'a> GitStatusInfo<'a> {
}
/// Gets the number of files in various git states (staged, modified, deleted, etc...)
fn get_repo_status(context: &Context, config: &GitStatusConfig) -> Option<RepoStatus> {
fn get_repo_status(
context: &Context,
repo: &context::Repo,
config: &GitStatusConfig,
) -> Option<RepoStatus> {
log::debug!("New repo status created");
let mut repo_status = RepoStatus::default();
let mut args = vec![
OsStr::new("-C"),
context.current_dir.as_os_str(),
OsStr::new("--no-optional-locks"),
OsStr::new("status"),
OsStr::new("--porcelain=2"),
];
let mut args = vec!["status", "--porcelain=2"];
// for performance reasons, only pass flags if necessary...
let has_ahead_behind = !config.ahead.is_empty() || !config.behind.is_empty();
let has_up_to_date_diverged = !config.up_to_date.is_empty() || !config.diverged.is_empty();
if has_ahead_behind || has_up_to_date_diverged {
args.push(OsStr::new("--branch"));
args.push("--branch");
}
// ... and add flags that omit information the user doesn't want
let has_untracked = !config.untracked.is_empty();
if !has_untracked {
args.push(OsStr::new("--untracked-files=no"));
args.push("--untracked-files=no");
}
if config.ignore_submodules {
args.push(OsStr::new("--ignore-submodules=dirty"));
args.push("--ignore-submodules=dirty");
} else if !has_untracked {
args.push(OsStr::new("--ignore-submodules=untracked"));
args.push("--ignore-submodules=untracked");
}
let status_output = context.exec_cmd("git", &args)?;
let status_output = repo.exec_git(context, &args)?;
let statuses = status_output.stdout.lines();
statuses.for_each(|status| {
@@ -243,8 +248,8 @@ fn get_repo_status(context: &Context, config: &GitStatusConfig) -> Option<RepoSt
Some(repo_status)
}
fn get_stashed_count(context: &Context) -> Option<usize> {
let repo = context.get_repo().ok()?.open();
fn get_stashed_count(repo: &context::Repo) -> Option<usize> {
let repo = repo.open();
let reference = match repo.try_find_reference("refs/stash") {
Ok(Some(reference)) => reference,
// No stash reference found
@@ -742,6 +747,37 @@ mod tests {
repo_dir.close()
}
#[test]
#[cfg(unix)]
fn doesnt_run_fsmonitor() -> io::Result<()> {
use std::os::unix::fs::PermissionsExt;
let repo_dir = fixture_repo(FixtureProvider::Git)?;
let mut f = File::create(repo_dir.path().join("do_not_execute"))?;
write!(f, "#!/bin/sh\necho executed > executed\nsync executed")?;
let metadata = f.metadata()?;
let mut permissions = metadata.permissions();
permissions.set_mode(0o700);
f.set_permissions(permissions)?;
f.sync_all()?;
create_command("git")?
.args(["config", "core.fsmonitor"])
.arg(repo_dir.path().join("do_not_execute"))
.current_dir(repo_dir.path())
.output()?;
ModuleRenderer::new("git_status")
.path(repo_dir.path())
.collect();
let created_file = repo_dir.path().join("executed").exists();
assert!(!created_file);
repo_dir.close()
}
#[test]
fn shows_stashed() -> io::Result<()> {
let repo_dir = fixture_repo(FixtureProvider::Git)?;