util.rs raw

   1  // Copyright (c) The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or https://opensource.org/license/mit/.
   4  
   5  use std::env;
   6  use std::path::PathBuf;
   7  use std::process::Command;
   8  
   9  /// A possible error returned by any of the linters.
  10  ///
  11  /// The error string should explain the failure type and list all violations.
  12  pub type LintError = String;
  13  pub type LintResult = Result<(), LintError>;
  14  pub type LintFn = fn() -> LintResult;
  15  
  16  /// Return the git command
  17  ///
  18  /// Lint functions should use this command, so that only files tracked by git are considered and
  19  /// temporary and untracked files are ignored. For example, instead of 'grep', 'git grep' should be
  20  /// used.
  21  pub fn git() -> Command {
  22      let mut git = Command::new("git");
  23      git.arg("--no-pager");
  24      git
  25  }
  26  
  27  /// Return stdout on success and a LintError on failure, when invalid UTF8 was detected or the
  28  /// command did not succeed.
  29  pub fn check_output(cmd: &mut Command) -> Result<String, LintError> {
  30      let out = cmd.output().expect("command error");
  31      if !out.status.success() {
  32          return Err(String::from_utf8_lossy(&out.stderr).to_string());
  33      }
  34      Ok(String::from_utf8(out.stdout)
  35          .map_err(|e| {
  36              format!("All path names, source code, messages, and output must be valid UTF8!\n{e}")
  37          })?
  38          .trim()
  39          .to_string())
  40  }
  41  
  42  /// Return the git root as utf8, or panic
  43  pub fn get_git_root() -> PathBuf {
  44      PathBuf::from(check_output(git().args(["rev-parse", "--show-toplevel"])).unwrap())
  45  }
  46  
  47  /// Return the commit range, or panic
  48  pub fn commit_range() -> String {
  49      // Use the env var, if set. E.g. COMMIT_RANGE='HEAD~n..HEAD' for the last 'n' commits.
  50      env::var("COMMIT_RANGE").unwrap_or_else(|_| {
  51          // Otherwise, assume that a merge commit exists. This merge commit is assumed
  52          // to be the base, after which linting will be done. If the merge commit is
  53          // HEAD, the range will be empty.
  54          format!(
  55              "{}..HEAD",
  56              check_output(git().args(["rev-list", "--max-count=1", "--merges", "HEAD"]))
  57                  .expect("check_output failed")
  58          )
  59      })
  60  }
  61  
  62  /// Return all subtree paths
  63  pub fn get_subtrees() -> Vec<&'static str> {
  64      // Keep in sync with [test/lint/README.md#git-subtree-checksh]
  65      vec![
  66          "src/crc32c",
  67          "src/crypto/ctaes",
  68          "src/ipc/libmultiprocess",
  69          "src/leveldb",
  70          "src/minisketch",
  71          "src/secp256k1",
  72      ]
  73  }
  74  
  75  /// Return the pathspecs to exclude by default
  76  pub fn get_pathspecs_default_excludes() -> Vec<String> {
  77      get_subtrees()
  78          .iter()
  79          .chain(&[
  80              "doc/release-notes/release-notes-*", // archived notes
  81          ])
  82          .map(|s| format!(":(exclude){s}"))
  83          .collect()
  84  }
  85