xtask/
fs_helpers.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Helper functions to traverse + enumerate the project's filesystem, used by
5//! multiple task implementations.
6
7use std::collections::BTreeSet;
8use std::path::PathBuf;
9
10/// Return a list of all files that are currently git diffed, including
11/// those which have been staged, but not yet been committed.
12pub fn git_diffed(in_git_hook: bool) -> anyhow::Result<Vec<PathBuf>> {
13    let sh = xshell::Shell::new()?;
14
15    let files = xshell::cmd!(sh, "git diff --diff-filter MAR --name-only")
16        .output()?
17        .stdout;
18    let files_cached = xshell::cmd!(sh, "git diff --diff-filter MAR --name-only --cached")
19        .output()?
20        .stdout;
21
22    let files = String::from_utf8_lossy(&files);
23    let files_cached = String::from_utf8_lossy(&files_cached);
24
25    // don't include unstaged files when running in a hook context
26    let files: Box<dyn Iterator<Item = _>> = if in_git_hook {
27        Box::new(files_cached.lines())
28    } else {
29        Box::new(files_cached.lines().chain(files.lines()))
30    };
31
32    let mut all_files = files.map(PathBuf::from).collect::<Vec<_>>();
33
34    all_files.sort();
35    all_files.dedup();
36    Ok(all_files)
37}
38
39/// Return files tracked by git (excluding those from .gitignore), including
40/// those which have not yet been staged / committed.
41pub fn git_ls_files() -> anyhow::Result<Vec<PathBuf>> {
42    let sh = xshell::Shell::new()?;
43
44    macro_rules! as_set {
45        ($cmd:literal) => {{
46            let output = xshell::cmd!(sh, $cmd).output()?.stdout;
47            let output = String::from_utf8_lossy(&output).to_string();
48            output
49                .split('\n')
50                .map(PathBuf::from)
51                .collect::<BTreeSet<_>>()
52        }};
53    }
54
55    // "extra" corresponds to files not-yet committed to git
56    let all = as_set!("git ls-files");
57    let extra = as_set!("git ls-files --others --exclude-standard");
58    let deleted = as_set!("git ls-files --deleted");
59
60    let mut allow_list = all;
61    allow_list.extend(extra);
62    allow_list = allow_list.difference(&deleted).cloned().collect();
63
64    // Vec is returned in sorted order because of BTreeSet iteration order
65    Ok(allow_list.into_iter().collect())
66}