aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/cmd
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-19 05:54:00 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-19 06:15:20 +0800
commitec9edc294fd5e7e29977fc7b0e6fb953422bc0e2 (patch)
treebbf89ef4457b8de8a8a9bca5668045d612d27572 /mingling_ci/src/cmd
parent06dfc27194c11e1d1033c292c759a1c5d82e780b (diff)
chore: reorganize dev tools into dev/ directory and consolidate configsHEADmain
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.
Diffstat (limited to 'mingling_ci/src/cmd')
-rw-r--r--mingling_ci/src/cmd/cmd_git_lock.rs77
-rw-r--r--mingling_ci/src/cmd/cmd_git_unlock.rs116
-rw-r--r--mingling_ci/src/cmd/cmd_report_clean.rs54
-rw-r--r--mingling_ci/src/cmd/cmd_report_collect.rs150
-rw-r--r--mingling_ci/src/cmd/cmd_show_features.rs26
-rw-r--r--mingling_ci/src/cmd/cmd_show_manifests.rs71
6 files changed, 0 insertions, 494 deletions
diff --git a/mingling_ci/src/cmd/cmd_git_lock.rs b/mingling_ci/src/cmd/cmd_git_lock.rs
deleted file mode 100644
index 0e9bf22..0000000
--- a/mingling_ci/src/cmd/cmd_git_lock.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use mingling::{
- Grouped, RenderResult, Routable,
- macros::{buffer, command, r_println, renderer},
- res::ResExitCode,
-};
-
-use crate::Next;
-use crate::git::{CI_TEMP_COMMIT_MESSAGE, LOCK_FILE, TEMP_COMMIT_MESSAGE, run_git, worktree_clean};
-use crate::res::{CargoError, MessagePrinter};
-
-/// Temporarily commits the workspace so CI can run on a stable tree.
-///
-/// First pins the current HEAD to the `mingling/bkup` backup branch (created
-/// or force-reset). When the tree is dirty, all changes are packed into a
-/// plain `TEMP` commit first so they can be restored later; the `CI TEMP`
-/// commit then carries only the `MINGLING-CI-CHECKING` marker file, whose
-/// content (`true`/`false`) tells `git-unlock` which restore path to take.
-#[command(node = "git-lock")]
-pub fn git_lock() -> Next {
- if let Err(e) = run_git(["branch", "-f", "mingling/bkup", "HEAD"]) {
- return ErrorGitLock(e).to_chain();
- }
-
- let dirty = !worktree_clean();
- if dirty {
- if let Err(e) = run_git(["add", "."]) {
- return ErrorGitLock(e).to_chain();
- }
- if let Err(e) = run_git(["commit", "-m", TEMP_COMMIT_MESSAGE]) {
- return ErrorGitLock(e).to_chain();
- }
- }
-
- let marker = if dirty { "true" } else { "false" };
- if let Err(e) = std::fs::write(LOCK_FILE, marker) {
- return ErrorGitLock(format!("failed to create {LOCK_FILE}: {e}")).to_chain();
- }
-
- if let Err(e) = run_git(["add", "."]) {
- return ErrorGitLock(e).to_chain();
- }
- if let Err(e) = run_git(["commit", "-m", CI_TEMP_COMMIT_MESSAGE]) {
- return ErrorGitLock(e).to_chain();
- }
-
- ResultGitLock { dirty }.to_chain()
-}
-
-/// Whether the tree was dirty (a base `TEMP` commit exists) when locking.
-#[derive(Grouped)]
-pub struct ResultGitLock {
- dirty: bool,
-}
-
-#[derive(Grouped, Default)]
-pub struct ErrorGitLock(pub String);
-
-#[renderer(buffer)]
-pub fn render_git_lock(r: ResultGitLock) {
- if r.dirty {
- r_println!("Locked: dirty workspace committed for CI");
- } else {
- r_println!("Locked: clean workspace marked for CI");
- }
-}
-
-#[renderer]
-pub fn render_error_git_lock(
- e: ErrorGitLock,
- error: &CargoError,
- exit_code: &mut ResExitCode,
-) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("Git-Lock: {}", e.0)]);
- exit_code.exit_code = 1;
- render_result
-}
diff --git a/mingling_ci/src/cmd/cmd_git_unlock.rs b/mingling_ci/src/cmd/cmd_git_unlock.rs
deleted file mode 100644
index 41efefc..0000000
--- a/mingling_ci/src/cmd/cmd_git_unlock.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use mingling::{
- Grouped, RenderResult, Routable,
- macros::{arg, buffer, command, r_println, renderer},
- picker::{EntryPicker, value::Flag},
- res::ResExitCode,
-};
-
-use crate::git::{LOCK_FILE, TEMP_COMMIT_MARK, head_message, run_git, worktree_clean};
-use crate::res::{CargoError, MessagePrinter};
-use crate::{Entry, Next};
-
-/// Undoes a CI temporary commit created by [`crate::cmd::cmd_git_lock`].
-///
-/// Only acts when the HEAD commit message contains `CI TEMP` (case-sensitive).
-/// The restore path is picked by the marker file content:
-///
-/// - `true`: a base `TEMP` commit with the dirty changes sits below; restore
-/// by hard-resetting past the marker commit, then soft-resetting and
-/// unstaging to put the user's changes back into the working tree.
-/// - `false`: the tree was clean; a single hard reset back to the original
-/// HEAD is enough.
-///
-/// When the working tree is dirty (e.g. CI left tracked changes behind) the
-/// restore still runs, but the command reports a non-zero exit code so the
-/// caller knows the CI phase contaminated the repository. With `--show-diff`
-/// the diff of those changes is printed before they are discarded.
-#[command(node = "git-unlock")]
-// `#[command]` rewrites an owned first param into the entry type, so the args
-// must be passed by value even though the body only reads them.
-#[allow(clippy::needless_pass_by_value)]
-pub fn git_unlock(args: Entry) -> Next {
- let head = head_message().unwrap_or_default();
- if !head.contains(TEMP_COMMIT_MARK) {
- return ErrorGitUnlock(format!("HEAD is not a CI temporary commit: `{head}`")).to_chain();
- }
-
- // Record dirtiness before restoring: the restore discards those changes.
- let dirty = !worktree_clean();
-
- // The marker file lives in the HEAD (CI TEMP) commit, so it is readable
- // from the working tree; a missing marker falls back to the clean path.
- let based_on_dirty =
- std::fs::read_to_string(LOCK_FILE).is_ok_and(|content| content.trim() == "true");
-
- if dirty && *args.pick(&arg![show_diff: Flag]).unwrap() {
- show_diff();
- }
-
- if let Err(e) = undo_ci_phase(based_on_dirty) {
- return ErrorGitUnlock(e).to_chain();
- }
-
- ResultGitUnlock { dirty }.to_chain()
-}
-
-/// Prints the tracked changes the CI run left behind, before the restore
-/// discards them. Untracked files are not shown (they are removed by clean).
-fn show_diff() {
- let Ok(diff) = run_git(["diff", "HEAD"]) else {
- return;
- };
- if diff.is_empty() {
- return;
- }
- println!("{diff}");
-}
-
-/// Restores the workspace, keeping the user's pre-lock changes.
-///
-/// With a base `TEMP` commit (`true`) the marker commit is dropped by a hard
-/// reset to `HEAD~1`, the `TEMP` commit is unwrapped into the staging area by
-/// a soft reset, and a plain reset unstages it back into the working tree.
-/// Without one (`false`) a single hard reset to `HEAD~1` removes the marker
-/// commit and lands on the original HEAD.
-fn undo_ci_phase(based_on_dirty: bool) -> Result<(), String> {
- run_git(["reset", "--hard", "HEAD~1"])?;
- if based_on_dirty {
- // Unwrap the `TEMP` commit into the staging area, then unstage it
- // back into the working tree.
- run_git(["reset", "--soft", "HEAD~1"])?;
- run_git(["reset"])?;
- }
- std::fs::remove_file(LOCK_FILE).ok();
- Ok(())
-}
-
-/// Whether the working tree was dirty when the unlock started.
-#[derive(Grouped)]
-pub struct ResultGitUnlock {
- dirty: bool,
-}
-
-#[derive(Grouped, Default)]
-pub struct ErrorGitUnlock(pub String);
-
-#[renderer(buffer)]
-pub fn render_git_unlock(r: ResultGitUnlock, exit_code: &mut ResExitCode) {
- if r.dirty {
- r_println!("Unlocked: workspace restored (working tree was dirty)");
- exit_code.exit_code = 1;
- } else {
- r_println!("Unlocked: workspace restored");
- }
-}
-
-#[renderer]
-pub fn render_error_git_unlock(
- e: ErrorGitUnlock,
- error: &CargoError,
- exit_code: &mut ResExitCode,
-) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("Git-Unlock: {}", e.0)]);
- exit_code.exit_code = 1;
- render_result
-}
diff --git a/mingling_ci/src/cmd/cmd_report_clean.rs b/mingling_ci/src/cmd/cmd_report_clean.rs
deleted file mode 100644
index 976851e..0000000
--- a/mingling_ci/src/cmd/cmd_report_clean.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-use std::path::PathBuf;
-
-use mingling::{
- Grouped, RenderResult, Routable,
- macros::{buffer, command, r_println, renderer},
-};
-
-use crate::Next;
-use crate::reporter::{COLLECT_DIR, REPORT_PATH};
-use crate::res::{CargoError, MessagePrinter};
-
-/// Removes collected logs and the generated report.
-#[command(node = "report-clean")]
-pub fn report_clean() -> Next {
- let mut removed = Vec::new();
- for path in [PathBuf::from(COLLECT_DIR), PathBuf::from(REPORT_PATH)] {
- match std::fs::remove_dir_all(&path).or_else(|_| std::fs::remove_file(&path)) {
- Ok(()) => removed.push(path),
- Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
- Err(e) => {
- return ErrorReportClean(format!("failed to remove {}: {e}", path.display()))
- .to_chain();
- }
- }
- }
- ResultReportClean { removed }.to_chain()
-}
-
-/// Paths removed by `report-clean`.
-#[derive(Grouped)]
-pub struct ResultReportClean {
- pub removed: Vec<PathBuf>,
-}
-
-#[derive(Grouped, Default)]
-pub struct ErrorReportClean(pub String);
-
-#[renderer(buffer)]
-pub fn render_report_clean(r: ResultReportClean) {
- if r.removed.is_empty() {
- r_println!("Report data already clean");
- } else {
- for path in r.removed {
- r_println!("Removed {}", path.display());
- }
- }
-}
-
-#[renderer]
-pub fn render_error_report_clean(e: ErrorReportClean, error: &CargoError) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("Report: {}", e.0)]);
- render_result
-}
diff --git a/mingling_ci/src/cmd/cmd_report_collect.rs b/mingling_ci/src/cmd/cmd_report_collect.rs
deleted file mode 100644
index 2eff074..0000000
--- a/mingling_ci/src/cmd/cmd_report_collect.rs
+++ /dev/null
@@ -1,150 +0,0 @@
-use std::collections::{BTreeMap, HashMap};
-use std::path::PathBuf;
-
-use just_template::Template;
-use mingling::{
- Grouped, RenderResult, Routable,
- macros::{buffer, command, r_println, renderer},
-};
-
-use crate::Next;
-use crate::reporter::{COLLECT_DIR, REPORT_PATH};
-use crate::res::{CargoError, MessagePrinter, ResCollectLogs};
-
-const REPORT_TEMPLATE: &str = include_str!("../../tmpls/report.md");
-const TASK_SECTION_TEMPLATE: &str = include_str!("../../tmpls/task_section.md");
-
-/// Maps a package to its per-OS pass/fail status.
-type OsStatuses = BTreeMap<String, bool>;
-
-/// A row in a task section: item name and its per-OS statuses.
-type TaskRow<'a> = (&'a String, &'a OsStatuses);
-
-/// Rows grouped by task name.
-type RowsByTask<'a> = BTreeMap<&'a String, Vec<TaskRow<'a>>>;
-
-#[command(node = "report-collect")]
-pub fn report_collect(logs: &ResCollectLogs) -> Next {
- if !PathBuf::from(COLLECT_DIR).is_dir() {
- return ErrorNoCollectDir.to_chain();
- }
-
- // Group rows by task: task -> [(item, os_statuses)].
- let by_task: RowsByTask =
- logs.statuses
- .iter()
- .fold(BTreeMap::new(), |mut acc, ((task, item), os_statuses)| {
- acc.entry(task).or_default().push((item, os_statuses));
- acc
- });
-
- // Render one section per task (table rows + this task's failures).
- let mut fail_count = 0;
- let mut sections: Vec<HashMap<String, String>> = Vec::new();
- for (task, rows) in by_task {
- let mut row_arms = Vec::new();
- let mut fail_arms = Vec::new();
- for (item, os_statuses) in rows {
- let location = logs
- .locations
- .get(&(task.clone(), item.clone()))
- .cloned()
- .unwrap_or_default();
- row_arms.push(HashMap::from([
- ("item_name".to_string(), item.clone()),
- ("location".to_string(), location),
- (
- "pass_win".to_string(),
- pass_cell(os_statuses.get("Windows")),
- ),
- (
- "pass_linux".to_string(),
- pass_cell(os_statuses.get("Linux")),
- ),
- ("pass_mac".to_string(), pass_cell(os_statuses.get("MacOS"))),
- ]));
-
- for (os, ok) in os_statuses {
- if !ok {
- let stdout = logs
- .err_outputs
- .get(&(task.clone(), os.clone(), item.clone()))
- .cloned()
- .unwrap_or_default();
- fail_arms.push(HashMap::from([
- ("item_name".to_string(), item.clone()),
- ("stdout".to_string(), stdout),
- ]));
- fail_count += 1;
- }
- }
- }
-
- let mut section = Template::from(TASK_SECTION_TEMPLATE);
- section.insert_param("task_name".to_string(), task.clone());
- *section.add_impl("rows".to_string()) = row_arms;
- *section.add_impl("fails".to_string()) = fail_arms;
- sections.push(HashMap::from([(
- "section".to_string(),
- section.expand().unwrap_or_default(),
- )]));
- }
-
- let mut template = Template::from(REPORT_TEMPLATE);
-
- template.insert_param("date".to_string(), logs.git.date.clone());
- template.insert_param("commit_hash".to_string(), logs.git.commit_hash.clone());
- *template.add_impl("task_sections".to_string()) = sections;
-
- let expanded = template.expand().unwrap_or_default();
- let output = PathBuf::from(REPORT_PATH);
- let parent = output.parent().expect("output path has a parent");
-
- if let Err(e) = std::fs::create_dir_all(parent).and_then(|()| std::fs::write(&output, expanded))
- {
- return ErrorReportWrite(format!("failed to write {}: {e}", output.display())).to_chain();
- }
-
- ResultCollectResults { output, fail_count }.to_chain()
-}
-
-fn pass_cell(status: Option<&bool>) -> String {
- match status {
- Some(true) => "✅".to_string(),
- Some(false) => "❌".to_string(),
- None => "—".to_string(),
- }
-}
-
-/// The generated report.
-#[derive(Grouped)]
-pub struct ResultCollectResults {
- pub output: PathBuf,
- pub fail_count: usize,
-}
-
-#[derive(Grouped, Default)]
-pub struct ErrorNoCollectDir;
-
-#[derive(Grouped, Default)]
-pub struct ErrorReportWrite(pub String);
-
-#[renderer(buffer)]
-pub fn render_collect_results(r: ResultCollectResults) {
- r_println!("Collected {} failing logs", r.fail_count);
- r_println!("Report generated at {}", r.output.display());
-}
-
-#[renderer]
-pub fn render_error_no_collect_dir(_: ErrorNoCollectDir, error: &CargoError) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("No collect directory: {COLLECT_DIR}")]);
- render_result
-}
-
-#[renderer]
-pub fn render_error_report_write(e: ErrorReportWrite, error: &CargoError) -> RenderResult {
- let render_result = RenderResult::new();
- error.println(vec![format!("Report: {}", e.0)]);
- render_result
-}
diff --git a/mingling_ci/src/cmd/cmd_show_features.rs b/mingling_ci/src/cmd/cmd_show_features.rs
deleted file mode 100644
index 5fff0c5..0000000
--- a/mingling_ci/src/cmd/cmd_show_features.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use mingling::{
- Grouped,
- macros::{buffer, command, r_println, renderer},
-};
-
-use crate::res::ResFeatureList;
-
-#[command(node = "show-features")]
-pub fn show_features(features: &ResFeatureList) -> ResultShowFeatures {
- ResultShowFeatures {
- features: features.list.clone(),
- }
-}
-
-/// The docs.rs feature list of `mingling`.
-#[derive(Grouped)]
-pub struct ResultShowFeatures {
- pub features: Vec<String>,
-}
-
-#[renderer(buffer)]
-pub fn render_show_features(r: ResultShowFeatures) {
- for feature in r.features {
- r_println!("{feature}");
- }
-}
diff --git a/mingling_ci/src/cmd/cmd_show_manifests.rs b/mingling_ci/src/cmd/cmd_show_manifests.rs
deleted file mode 100644
index 2be82d2..0000000
--- a/mingling_ci/src/cmd/cmd_show_manifests.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use std::path::PathBuf;
-
-use mingling::{
- Grouped,
- macros::{buffer, command, r_println, renderer},
-};
-
-use prettytable::{
- Cell, Row, Table,
- format::{FormatBuilder, LinePosition, LineSeparator},
-};
-
-use crate::res::Manifests;
-
-#[command(node = "show-manifests")]
-pub fn show_manifests(manifests: &Manifests) -> ResultPrintManifests {
- let mut entries: Vec<ManifestEntry> = manifests
- .package_dirs
- .iter()
- .map(|(name, path)| ManifestEntry {
- name: name.clone(),
- path: path.clone(),
- })
- .collect();
- entries.sort_by(|a, b| a.path.cmp(&b.path));
- ResultPrintManifests { entries }
-}
-
-/// All manifests the CI will check, sorted by path.
-#[derive(Grouped)]
-pub struct ResultPrintManifests {
- pub entries: Vec<ManifestEntry>,
-}
-
-#[derive(Debug, Clone)]
-pub struct ManifestEntry {
- pub name: String,
- pub path: PathBuf,
-}
-
-#[renderer(buffer)]
-pub fn render_print_manifests(r: ResultPrintManifests) {
- let mut table = Table::new();
-
- table.set_format(
- FormatBuilder::new()
- .column_separator('│')
- .borders('│')
- .separator(LinePosition::Top, LineSeparator::new('─', '┬', '┌', '┐'))
- .separator(LinePosition::Title, LineSeparator::new('─', '┼', '├', '┤'))
- .separator(LinePosition::Bottom, LineSeparator::new('─', '┴', '└', '┘'))
- .padding(1, 1)
- .build(),
- );
-
- table.set_titles(Row::new(vec![
- Cell::new("#"),
- Cell::new("Package-Name"),
- Cell::new("Package-Path"),
- ]));
-
- for (index, entry) in r.entries.iter().enumerate() {
- table.add_row(Row::new(vec![
- Cell::new(&(index + 1).to_string()),
- Cell::new(&entry.name),
- Cell::new(&entry.path.to_string_lossy()),
- ]));
- }
-
- r_println!("{table}");
-}