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:
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user