mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:23:33 +00:00
feat(core/git): after creating worktree, ensure 'origin' remote exists by aliasing a writable remote (fork/upstream-push) and set origin/HEAD for default-branch detection; improves default git command UX in worktrees
This commit is contained in:
@@ -126,9 +126,85 @@ pub async fn setup_worktree(git_root: &Path, branch_id: &str) -> Result<(PathBuf
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure an 'origin' remote exists and points to a writable remote so that
|
||||
// generic commands like `git push origin HEAD:main` work out of the box.
|
||||
ensure_origin_remote(git_root).await.ok();
|
||||
|
||||
Ok((worktree_path, effective_branch))
|
||||
}
|
||||
|
||||
/// Ensure a remote named `origin` exists. If it's missing, choose a likely
|
||||
/// writable remote (prefer `fork`, then `upstream-push`, then the first push
|
||||
/// URL we find) and alias it as `origin`. Finally, set the remote HEAD so
|
||||
/// `origin/HEAD` points at the default branch.
|
||||
async fn ensure_origin_remote(git_root: &Path) -> Result<(), String> {
|
||||
// Check existing remotes
|
||||
let remotes_out = Command::new("git")
|
||||
.current_dir(git_root)
|
||||
.args(["remote"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("git remote failed: {}", e))?;
|
||||
if !remotes_out.status.success() {
|
||||
return Err("git remote returned error".to_string());
|
||||
}
|
||||
let remotes: Vec<String> = String::from_utf8_lossy(&remotes_out.stdout)
|
||||
.lines()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
if remotes.iter().any(|r| r == "origin") {
|
||||
// Make sure origin/HEAD is set; ignore errors
|
||||
let _ = Command::new("git")
|
||||
.current_dir(git_root)
|
||||
.args(["remote", "set-head", "origin", "-a"])
|
||||
.output()
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Prefer candidates in this order
|
||||
let mut candidates = vec!["fork", "upstream-push", "upstream"]; // typical setups
|
||||
// Append any other remotes as fallbacks
|
||||
for r in &remotes {
|
||||
if !candidates.contains(&r.as_str()) { candidates.push(r); }
|
||||
}
|
||||
|
||||
// Find a candidate with a URL
|
||||
for cand in candidates {
|
||||
let url_out = Command::new("git")
|
||||
.current_dir(git_root)
|
||||
.args(["remote", "get-url", cand])
|
||||
.output()
|
||||
.await;
|
||||
if let Ok(out) = url_out {
|
||||
if out.status.success() {
|
||||
let url = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
if !url.is_empty() {
|
||||
// Add origin pointing to this URL
|
||||
let add = Command::new("git")
|
||||
.current_dir(git_root)
|
||||
.args(["remote", "add", "origin", &url])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("git remote add origin failed: {}", e))?;
|
||||
if !add.status.success() {
|
||||
return Err("failed to add origin".to_string());
|
||||
}
|
||||
let _ = Command::new("git")
|
||||
.current_dir(git_root)
|
||||
.args(["remote", "set-head", "origin", "-a"])
|
||||
.output()
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No usable remote found; leave as-is
|
||||
Err("no suitable remote to alias as origin".to_string())
|
||||
}
|
||||
|
||||
/// Copy uncommitted (modified + untracked) files from `src_root` into the `worktree_path`.
|
||||
/// Returns the number of files copied.
|
||||
pub async fn copy_uncommitted_to_worktree(src_root: &Path, worktree_path: &Path) -> Result<usize, String> {
|
||||
|
||||
@@ -872,7 +872,7 @@ impl App<'_> {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
if let Some(prompt) = initial_prompt {
|
||||
if !prompt.is_empty() {
|
||||
let preface = "[internal] When you finish this task, ask the user if they want any changes. If they are happy, offer to merge the branch back into the repository's default branch and delete the worktree. Wait for explicit confirmation before merging.".to_string();
|
||||
let preface = "[internal] When you finish this task, ask the user if they want any changes. If they are happy, offer to merge the branch back into the repository's default branch and delete the worktree. Use '/branch finalize' (or an equivalent git worktree remove + switch) rather than deleting the folder directly so the UI can switch back cleanly. Wait for explicit confirmation before merging.".to_string();
|
||||
widget.submit_text_message_with_preface(prompt, preface);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1891,6 +1891,36 @@ impl ChatWidget<'_> {
|
||||
// Fade the welcome cell only when a user actually posts a message.
|
||||
for cell in &self.history_cells { cell.trigger_fade(); }
|
||||
let UserMessage { display_text, mut ordered_items } = user_message;
|
||||
// If our configured cwd no longer exists (e.g., a worktree folder was
|
||||
// deleted outside the app), try to automatically recover to the repo
|
||||
// root for worktrees and re-submit the same message there.
|
||||
if !self.config.cwd.exists() {
|
||||
let missing = self.config.cwd.clone();
|
||||
let missing_s = missing.display().to_string();
|
||||
if missing_s.contains("/.code/branches/") {
|
||||
// Recover by walking up to '<repo>/.code/branches/<branch>' -> repo root
|
||||
let mut anc = missing.as_path();
|
||||
// Walk up 3 parents if available
|
||||
for _ in 0..3 { if let Some(p) = anc.parent() { anc = p; } }
|
||||
let fallback_root = anc.to_path_buf();
|
||||
if fallback_root.exists() {
|
||||
use codex_core::protocol::{BackgroundEventEvent, Event, EventMsg};
|
||||
let msg = format!("⚠️ Worktree directory is missing: {}\nSwitching to repo root: {}",
|
||||
missing.display(), fallback_root.display());
|
||||
let _ = self.app_event_tx.send(AppEvent::CodexEvent(Event { id: "cwd-recover".to_string(), event_seq: 0, msg: EventMsg::BackgroundEvent(BackgroundEventEvent { message: msg }), order: None }));
|
||||
// Re-submit this exact message after switching cwd
|
||||
self.app_event_tx
|
||||
.send(AppEvent::SwitchCwd(fallback_root, Some(display_text.clone())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we can't recover, surface an error and drop the message to prevent loops
|
||||
self.history_push(history_cell::new_error_event(format!(
|
||||
"Working directory is missing: {}",
|
||||
self.config.cwd.display()
|
||||
)));
|
||||
return;
|
||||
}
|
||||
let original_text = display_text.clone();
|
||||
// Build a combined string view of the text-only parts to process slash commands
|
||||
let mut text_only = String::new();
|
||||
|
||||
Reference in New Issue
Block a user