Add Pending changelists view (option B)

The Changes tab now shows committed-but-not-submitted changelists as cards
above the working set: expandable to their files (click to preview), each
with per-CL Submit, plus Edit description / Uncommit (reopen -c default) /
Discard in a ⋯ menu, and a Submit-all button. Backend adds p4_reopen_default
and p4_set_desc; a small text-prompt modal edits descriptions. Preview panel
now shows either the working selection or a clicked pending-CL file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-07 16:34:59 +03:00
parent 1675a7cd49
commit de6d55ad84
5 changed files with 232 additions and 6 deletions

View File

@ -480,6 +480,69 @@ async fn p4_submit_change(state: State<'_, AppState>, change: String) -> Result<
run_stream(&conn, &["submit", "-c", &change], "submit")
}
/// "Uncommit": move files back into the default changelist (like `git reset`).
/// The now-empty numbered changelist is cleaned up on the next `p4_changes`.
#[tauri::command]
async fn p4_reopen_default(state: State<'_, AppState>, files: Vec<String>) -> Result<String, String> {
let conn = current(&state)?;
if files.is_empty() {
return Ok(String::new());
}
let mut args: Vec<&str> = vec!["reopen", "-c", "default"];
for f in &files {
args.push(f.as_str());
}
run_text(&conn, &args)
}
/// Edit the description of a pending changelist, preserving its file list.
#[tauri::command]
async fn p4_set_desc(state: State<'_, AppState>, change: String, description: String) -> Result<String, String> {
let conn = current(&state)?;
let spec = run_text(&conn, &["change", "-o", &change])?;
// rebuild the spec, replacing only the Description field's indented body
let mut out = String::new();
let mut lines = spec.lines().peekable();
let mut replaced = false;
while let Some(line) = lines.next() {
if !replaced && line.starts_with("Description:") {
out.push_str("Description:\n");
for dl in description.trim().lines() {
out.push('\t');
out.push_str(dl);
out.push('\n');
}
// consume the old (indented / blank) description body
while let Some(p) = lines.peek() {
if p.starts_with('\t') || p.starts_with(' ') || p.is_empty() {
lines.next();
} else {
break;
}
}
replaced = true;
} else {
out.push_str(line);
out.push('\n');
}
}
let mut child = p4_cmd(&conn, false)
.args(["change", "-i"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to start p4: {e}"))?;
if let Some(si) = child.stdin.as_mut() {
si.write_all(out.as_bytes()).map_err(|e| e.to_string())?;
}
let res = child.wait_with_output().map_err(|e| e.to_string())?;
if !res.status.success() {
return Err(String::from_utf8_lossy(&res.stderr).trim().to_string());
}
Ok(String::from_utf8_lossy(&res.stdout).trim().to_string())
}
/// Submit the selected files with a description.
#[tauri::command]
async fn p4_submit(
@ -895,6 +958,8 @@ pub fn run() {
p4_submit,
p4_commit,
p4_submit_change,
p4_reopen_default,
p4_set_desc,
p4_add,
p4_edit,
p4_delete,