mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Add worktree session creation to the agents overview (#45276)
## What changed - Add a configurable `new_worktree` action, bound to `w`, for local sessions with worktree support enabled. - Create the worktree from the cached project default branch, preferring remote `HEAD` and falling back to conventional `main` or `master` refs without fetching. Preserve the selected subdirectory and source checkout edits. - Start and bind a blank session in the new worktree, preserving the selected approval reviewer without sending an initial turn. - Show creation progress, pause conflicting actions, clean up unclaimed clean worktrees, and report retained checkout paths when session startup fails. Clear pending creation state on reconnect. ## Testing Add coverage for default-branch selection, source checkout preservation, session ownership and approval settings, cleanup, startup failures, reconnect state, and busy-state rendering. Extend Git environment isolation coverage and test unrelated non-UTF-8 refs. GitOrigin-RevId: 771ddc12d04dbf0d12e77e9c96c8386cf31e67c5
This commit is contained in:
@@ -94,6 +94,51 @@ pub(crate) fn git_path_from_bytes(bytes: &[u8]) -> Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the cached repository default without fetching or inheriting Git selectors.
|
||||
/// Prefer a remote HEAD (origin first), then conventional main/master refs.
|
||||
pub fn default_worktree_base(cwd: &Path) -> Result<String> {
|
||||
let output = git_output(
|
||||
cwd,
|
||||
GitOperation::Metadata,
|
||||
[
|
||||
"for-each-ref",
|
||||
"--format=%(refname) %(symref)",
|
||||
"refs/remotes",
|
||||
"refs/heads",
|
||||
],
|
||||
)?;
|
||||
// Unrelated refs may contain non-UTF-8 bytes. Decode only the chosen base,
|
||||
// because CreateWorktree accepts a UTF-8 revision.
|
||||
let branches = output
|
||||
.stdout
|
||||
.split(|byte| *byte == b'\n')
|
||||
.filter_map(|line| {
|
||||
let line = line.strip_suffix(b"\r").unwrap_or(line);
|
||||
let separator = line.iter().position(|byte| *byte == b' ')?;
|
||||
Some((&line[..separator], &line[separator + 1..]))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let base = branches
|
||||
.iter()
|
||||
.filter(|(name, target)| name.ends_with(b"/HEAD") && !target.is_empty())
|
||||
.min_by_key(|(name, _)| (!name.starts_with(b"refs/remotes/origin/"), *name))
|
||||
.map(|(_, target)| *target)
|
||||
.or_else(|| {
|
||||
[
|
||||
b"refs/remotes/origin/main".as_slice(),
|
||||
b"refs/remotes/origin/master".as_slice(),
|
||||
b"refs/heads/main".as_slice(),
|
||||
b"refs/heads/master".as_slice(),
|
||||
]
|
||||
.into_iter()
|
||||
.find(|candidate| branches.iter().any(|(branch, _)| branch == candidate))
|
||||
})
|
||||
.context("Could not determine the project's default branch.")?;
|
||||
std::str::from_utf8(base)
|
||||
.context("The project's default branch is not valid UTF-8.")
|
||||
.map(str::to_owned)
|
||||
}
|
||||
|
||||
fn base_git_command(cwd: &Path) -> Command {
|
||||
let mut command = Command::new("git");
|
||||
// Git wrappers export repository-local state to their children. Select this
|
||||
|
||||
@@ -166,3 +166,33 @@ fn write_marker_script(path: &Path, marker: &Path) -> Result<()> {
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(0o755))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn default_base_ignores_unrelated_non_utf8_refs() -> Result<()> {
|
||||
let (_directory, root) = repository()?;
|
||||
let head = git_stdout(&root, ["rev-parse", "HEAD"])?;
|
||||
// Packed refs work even on filesystems that cannot store non-UTF-8 filenames.
|
||||
fs::write(
|
||||
root.join(".git/packed-refs"),
|
||||
[head.as_bytes(), b" refs/heads/unrelated-\xff\n"].concat(),
|
||||
)?;
|
||||
assert_eq!(super::default_worktree_base(&root)?, "refs/heads/main");
|
||||
setup_git(
|
||||
&root,
|
||||
&["update-ref", "refs/remotes/origin/release/stable", "HEAD"],
|
||||
)?;
|
||||
setup_git(
|
||||
&root,
|
||||
&[
|
||||
"symbolic-ref",
|
||||
"refs/remotes/origin/HEAD",
|
||||
"refs/remotes/origin/release/stable",
|
||||
],
|
||||
)?;
|
||||
assert_eq!(
|
||||
super::default_worktree_base(&root)?,
|
||||
"refs/remotes/origin/release/stable"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::path::Component;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub use git::default_worktree_base;
|
||||
pub use settings::DEFAULT_WORKTREE_KEEP_COUNT;
|
||||
pub use settings::WorktreeSettings;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user