From ec9edc294fd5e7e29977fc7b0e6fb953422bc0e2 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 19 Aug 2026 05:54:00 +0800 Subject: chore: reorganize dev tools into dev/ directory and consolidate configs Move CI, dev tools, and configs from root-level scattered locations into a unified `dev/` directory structure. Update all references across build scripts, documentation, and editor configurations. Also consolidate editor config generation into build.rs for automated synchronization of rust-analyzer settings across VS Code and Zed. --- dev/ci/src/git.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 dev/ci/src/git.rs (limited to 'dev/ci/src/git.rs') diff --git a/dev/ci/src/git.rs b/dev/ci/src/git.rs new file mode 100644 index 0000000..a6fab2c --- /dev/null +++ b/dev/ci/src/git.rs @@ -0,0 +1,69 @@ +//! Thin wrappers around the `git` CLI used by the CI phase lock/unlock pair. + +use std::ffi::OsStr; +use std::process::Command; + +/// Marker file created by `git-lock` in the CI temporary commit; its content +/// is `true` when the tree was dirty (a base TEMP commit exists below) or +/// `false` when it was clean. `git-unlock` reads it to pick the restore path. +pub(crate) const LOCK_FILE: &str = "MINGLING-CI-CHECKING"; + +/// First temporary commit: packs the dirty workspace changes so they can be +/// restored later. Only created when the tree is dirty. +pub(crate) const TEMP_COMMIT_MESSAGE: &str = "[DO NOT PUSH] TEMP [DO NOT PUSH]"; + +/// Second temporary commit: carries the marker file, and its message is what +/// `git-unlock` matches to confirm the CI phase. +pub(crate) const CI_TEMP_COMMIT_MESSAGE: &str = "[DO NOT PUSH] CI TEMP [DO NOT PUSH]"; + +/// Case-sensitive substring that identifies a CI temporary commit in the HEAD +/// commit message. +pub(crate) const TEMP_COMMIT_MARK: &str = "CI TEMP"; + +/// Runs `git `, returning stdout on success. +/// +/// # Errors +/// +/// Returns the git error message (stderr) when the command exits non-zero, or +/// when git itself cannot be spawned. +pub(crate) fn run_git(args: I) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let output = Command::new("git") + .args(args) + .output() + .map_err(|e| format!("failed to run git: {e}"))?; + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).into_owned()) + } else { + Err(String::from_utf8_lossy(&output.stderr).trim().to_string()) + } +} + +/// Returns `true` when the working tree has no tracked changes relative to +/// HEAD. Git failures count as "not clean" so the caller falls back to the +/// marker-file path. +/// +/// Uses the porcelain `git diff --quiet HEAD` rather than the plumbing +/// `git diff-index --quiet HEAD`: after a full compile the source files' +/// mtimes can be newer than the index stat records even though their content +/// is unchanged, and `diff-index` reports that stale stat as a change. The +/// porcelain diff refreshes the index first (via `diff.autoRefreshIndex`), +/// so it only reports real content differences. +pub(crate) fn worktree_clean() -> bool { + Command::new("git") + .args(["diff", "--quiet", "HEAD", "--"]) + .status() + .is_ok_and(|status| status.success()) +} + +/// The subject line of the HEAD commit. +/// +/// # Errors +/// +/// Returns the git error message when the log command fails. +pub(crate) fn head_message() -> Result { + run_git(["log", "-1", "--pretty=%s"]).map(|subject| subject.trim().to_string()) +} -- cgit