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
//! Saving and loading of on-disk information for the `git branchless test`
//! subcommand. This isn't part of Git itself, but multiple `git-branchless`
//! subsystems need to know about it, similar to snapshotting.
//!
//! Regrettably, this adds `serde` as a new dependency to `git-branchless-lib`,
//! which will increase build times.
use std::path::PathBuf;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use super::{Commit, NonZeroOid, Repo};
/// The exit status to use when a test command succeeds.
pub const TEST_SUCCESS_EXIT_CODE: i32 = 0;
/// The exit status to use when a test command intends to skip the provided commit.
/// This exit code is used officially by several source control systems:
///
/// - Git: "Note that the script (my_script in the above example) should exit
/// with code 0 if the current source code is good/old, and exit with a code
/// between 1 and 127 (inclusive), except 125, if the current source code is
/// bad/new."
/// - Mercurial: "The exit status of the command will be used to mark revisions
/// as good or bad: status 0 means good, 125 means to skip the revision, 127
/// (command not found) will abort the bisection, and any other non-zero exit
/// status means the revision is bad."
///
/// And it's become the de-facto standard for custom bisection scripts for other
/// source control systems as well.
pub const TEST_INDETERMINATE_EXIT_CODE: i32 = 125;
/// Similarly to `INDETERMINATE_EXIT_CODE`, this exit code is used officially by
/// `git-bisect` and others to abort the process. It's also typically raised by
/// the shell when the command is not found, so it's technically ambiguous
/// whether the command existed or not. Nonetheless, it's intuitive for a
/// failure to run a given command to abort the process altogether, so it
/// shouldn't be too confusing in practice.
pub const TEST_ABORT_EXIT_CODE: i32 = 127;
/// Convert a command string into a string that's safe to use as a filename.
pub fn make_test_command_slug(command: String) -> String {
command.replace(['/', ' ', '\n'], "__")
}
/// A version of `NonZeroOid` that can be serialized and deserialized. This
/// exists in case we want to move this type (back) into a separate module which
/// has a `serde` dependency in the interest of improving build times.
#[derive(Debug)]
pub struct SerializedNonZeroOid(pub NonZeroOid);
impl Serialize for SerializedNonZeroOid {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0.to_string())
}
}
impl<'de> Deserialize<'de> for SerializedNonZeroOid {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
let oid: NonZeroOid = s.parse().map_err(|_| {
de::Error::invalid_value(de::Unexpected::Str(&s), &"a valid non-zero OID")
})?;
Ok(SerializedNonZeroOid(oid))
}
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct SerializedTestResult {
pub command: String,
pub exit_code: i32,
pub fixed_tree_oid: Option<SerializedNonZeroOid>,
#[serde(default)]
pub interactive: bool,
}
/// Get the directory where the results of running tests are stored.
fn get_test_dir(repo: &Repo) -> PathBuf {
repo.get_path().join("branchless").join("test")
}
/// Get the directory where the result of tests for a particular commit are
/// stored. Tests are keyed by tree OID, not commit OID, so that they can be
/// cached based on the contents of the commit, rather than its specific commit
/// hash. This means that we can cache the results of tests for commits that
/// have been amended or rebased.
pub fn get_test_tree_dir(repo: &Repo, commit: &Commit) -> PathBuf {
get_test_dir(repo).join(commit.get_tree_oid().to_string())
}
/// Get the directory where the locks for running tests are stored.
pub fn get_test_locks_dir(repo: &Repo) -> PathBuf {
get_test_dir(repo).join("locks")
}
/// Get the directory where the worktrees for running tests are stored.
pub fn get_test_worktrees_dir(repo: &Repo) -> PathBuf {
get_test_dir(repo).join("worktrees")
}
/// Get the path to the file where the latest test command is stored.
pub fn get_latest_test_command_path(repo: &Repo) -> PathBuf {
get_test_dir(repo).join("latest-command")
}