aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/cmd
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-18 11:42:03 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-18 11:42:03 +0800
commitd41a33f7edc860624e4fb67000e2fc470a8d0df4 (patch)
tree8e83e1165121b353bd65bae85c41b37db85e9556 /mingling_ci/src/cmd
parent8e92f02cc12d35985cefebfe0f3ab54a94a0aa97 (diff)
feat(ci-new): preserve dirty changes across CI lock
Store dirty workspace changes in a temporary commit so `git-unlock` can restore them, with the marker file content (`true`/`false`) recording which restore path to take.
Diffstat (limited to 'mingling_ci/src/cmd')
-rw-r--r--mingling_ci/src/cmd/cmd_git_lock.rs41
-rw-r--r--mingling_ci/src/cmd/cmd_git_unlock.rs39
2 files changed, 57 insertions, 23 deletions
diff --git a/mingling_ci/src/cmd/cmd_git_lock.rs b/mingling_ci/src/cmd/cmd_git_lock.rs
index 4398593..0e9bf22 100644
--- a/mingling_ci/src/cmd/cmd_git_lock.rs
+++ b/mingling_ci/src/cmd/cmd_git_lock.rs
@@ -5,46 +5,63 @@ use mingling::{
};
use crate::Next;
-use crate::git::{LOCK_FILE, TEMP_COMMIT_MESSAGE, run_git, worktree_clean};
+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), then commits everything with a `[DO NOT PUSH] CI TEMP`
-/// message. When the tree has no tracked changes, a `MINGLING-CI-CHECKING`
-/// marker file is created first so the commit is never empty.
+/// 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();
}
- if worktree_clean()
- && let Err(e) = std::fs::write(LOCK_FILE, "")
- {
+ 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", TEMP_COMMIT_MESSAGE]) {
+ if let Err(e) = run_git(["commit", "-m", CI_TEMP_COMMIT_MESSAGE]) {
return ErrorGitLock(e).to_chain();
}
- ResultGitLock {}.to_chain()
+ ResultGitLock { dirty }.to_chain()
}
+/// Whether the tree was dirty (a base `TEMP` commit exists) when locking.
#[derive(Grouped)]
-pub struct ResultGitLock;
+pub struct ResultGitLock {
+ dirty: bool,
+}
#[derive(Grouped, Default)]
pub struct ErrorGitLock(pub String);
#[renderer(buffer)]
-pub fn render_git_lock(_: ResultGitLock) {
- r_println!("Locked: CI temp commit created");
+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]
diff --git a/mingling_ci/src/cmd/cmd_git_unlock.rs b/mingling_ci/src/cmd/cmd_git_unlock.rs
index 4d9ee80..6d7389d 100644
--- a/mingling_ci/src/cmd/cmd_git_unlock.rs
+++ b/mingling_ci/src/cmd/cmd_git_unlock.rs
@@ -10,11 +10,14 @@ use crate::res::{CargoError, MessagePrinter};
/// Undoes a CI temporary commit created by [`crate::cmd::cmd_git_lock`].
///
-/// Only acts when the HEAD commit message contains `CI TEMP` (case-sensitive),
-/// which together with the `MINGLING-CI-CHECKING` marker means the workspace
-/// is in a CI phase and all uncommitted state may be discarded. Restores the
-/// tree in five steps: unstage, restore tracked files, delete untracked files,
-/// roll back the temporary commit, and remove the marker file.
+/// 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
@@ -29,19 +32,33 @@ pub fn git_unlock() -> Next {
// Record dirtiness before restoring: the restore discards those changes.
let dirty = !worktree_clean();
- if let Err(e) = undo_ci_phase() {
+ // 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 let Err(e) = undo_ci_phase(based_on_dirty) {
return ErrorGitUnlock(e).to_chain();
}
ResultGitUnlock { dirty }.to_chain()
}
-/// The five-step restoration sequence of `git-unlock`.
-fn undo_ci_phase() -> Result<(), String> {
- run_git(["reset"])?;
- run_git(["restore", "."])?;
- run_git(["clean", "-f", "-d"])?;
+/// 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(())
}