mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +00:00
Add worktree settings parser (#40624)
## What changed - Add the `codex-worktree` crate for resolving managed worktree settings from the existing `[desktop]` configuration. - Default the worktree root to `$CODEX_HOME/worktrees`, automatic cleanup to enabled, and retained worktrees to 15. - Validate configured roots, cleanup flags, and retention counts before exposing the effective settings. ## Testing - Cover defaults, configured values, and invalid root and retention settings. GitOrigin-RevId: 1b279c62ef41e3c8eb540ce9beaf0f6cd96d903f
This commit is contained in:
committed by
copyberry
parent
4b81410a80
commit
f832b2fe7b
11
codex-rs/Cargo.lock
generated
11
codex-rs/Cargo.lock
generated
@@ -4844,6 +4844,17 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-worktree"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.5"
|
||||
|
||||
@@ -98,6 +98,7 @@ members = [
|
||||
"tools",
|
||||
"v8-poc",
|
||||
"websocket-client",
|
||||
"worktree",
|
||||
"workload-identity",
|
||||
"utils/absolute-path",
|
||||
"utils/audio",
|
||||
|
||||
6
codex-rs/worktree/BUILD.bazel
Normal file
6
codex-rs/worktree/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "worktree",
|
||||
crate_name = "codex_worktree",
|
||||
)
|
||||
21
codex-rs/worktree/Cargo.toml
Normal file
21
codex-rs/worktree/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "codex-worktree"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
test = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
4
codex-rs/worktree/src/lib.rs
Normal file
4
codex-rs/worktree/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod settings;
|
||||
|
||||
pub use settings::DEFAULT_WORKTREE_KEEP_COUNT;
|
||||
pub use settings::WorktreeSettings;
|
||||
83
codex-rs/worktree/src/settings.rs
Normal file
83
codex-rs/worktree/src/settings.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
//! Parses and validates worktree settings from the existing `[desktop]` config.
|
||||
//!
|
||||
//! Resolves the managed worktree root and the automatic-cleanup and retention
|
||||
//! settings without introducing a separate configuration format.
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::bail;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const WORKTREE_ROOT: &str = "git-worktree-root";
|
||||
const AUTO_CLEANUP: &str = "worktree-auto-cleanup-enabled";
|
||||
const KEEP_COUNT: &str = "worktree-keep-count";
|
||||
|
||||
pub const DEFAULT_WORKTREE_KEEP_COUNT: usize = 15;
|
||||
|
||||
/// Effective host-local settings already understood by Codex Desktop.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WorktreeSettings {
|
||||
pub root: PathBuf,
|
||||
pub auto_cleanup_enabled: bool,
|
||||
pub keep_count: usize,
|
||||
}
|
||||
|
||||
impl WorktreeSettings {
|
||||
/// Resolves existing `[desktop]` values without introducing another config format.
|
||||
pub fn from_desktop_config(
|
||||
codex_home: &Path,
|
||||
desktop: Option<&HashMap<String, Value>>,
|
||||
) -> Result<Self> {
|
||||
let root = match desktop.and_then(|settings| settings.get(WORKTREE_ROOT)) {
|
||||
None | Some(Value::Null) => codex_home.join("worktrees"),
|
||||
Some(value) => {
|
||||
let configured = value
|
||||
.as_str()
|
||||
.context("desktop.git-worktree-root must be a string")?
|
||||
.trim();
|
||||
if configured.is_empty() {
|
||||
codex_home.join("worktrees")
|
||||
} else {
|
||||
let path = PathBuf::from(configured);
|
||||
if !path.is_absolute() {
|
||||
bail!("desktop.git-worktree-root must be an absolute path");
|
||||
}
|
||||
path
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let auto_cleanup_enabled = desktop
|
||||
.and_then(|settings| settings.get(AUTO_CLEANUP))
|
||||
.map(|value| {
|
||||
value
|
||||
.as_bool()
|
||||
.context("desktop.worktree-auto-cleanup-enabled must be a boolean")
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(true);
|
||||
|
||||
let keep_count = desktop
|
||||
.and_then(|settings| settings.get(KEEP_COUNT))
|
||||
.map(|value| {
|
||||
let count = value
|
||||
.as_u64()
|
||||
.context("desktop.worktree-keep-count must be a positive integer")?;
|
||||
if count == 0 {
|
||||
bail!("desktop.worktree-keep-count must be a positive integer");
|
||||
}
|
||||
usize::try_from(count).context("desktop.worktree-keep-count is too large")
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(DEFAULT_WORKTREE_KEEP_COUNT);
|
||||
|
||||
Ok(Self {
|
||||
root: dunce::simplified(&root).to_path_buf(),
|
||||
auto_cleanup_enabled,
|
||||
keep_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
76
codex-rs/worktree/tests/settings.rs
Normal file
76
codex-rs/worktree/tests/settings.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use codex_worktree::WorktreeSettings;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn desktop_config(entries: [(&str, Value); 3]) -> HashMap<String, Value> {
|
||||
entries
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.to_owned(), value))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_settings_match_desktop_worktree_defaults() {
|
||||
let codex_home = tempfile::tempdir().expect("create Codex home");
|
||||
|
||||
let settings = WorktreeSettings::from_desktop_config(codex_home.path(), /*desktop*/ None)
|
||||
.expect("load default worktree settings");
|
||||
|
||||
assert_eq!(
|
||||
settings,
|
||||
WorktreeSettings {
|
||||
root: codex_home.path().join("worktrees"),
|
||||
auto_cleanup_enabled: true,
|
||||
keep_count: 15,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_settings_use_existing_root_and_retention_keys() {
|
||||
let codex_home = tempfile::tempdir().expect("create Codex home");
|
||||
let custom_root = codex_home.path().join("custom-managed-worktrees");
|
||||
let desktop = desktop_config([
|
||||
(
|
||||
"git-worktree-root",
|
||||
json!(custom_root.to_string_lossy().into_owned()),
|
||||
),
|
||||
("worktree-auto-cleanup-enabled", json!(false)),
|
||||
("worktree-keep-count", json!(4)),
|
||||
]);
|
||||
|
||||
let settings = WorktreeSettings::from_desktop_config(codex_home.path(), Some(&desktop))
|
||||
.expect("load existing Desktop worktree settings");
|
||||
|
||||
assert_eq!(
|
||||
settings,
|
||||
WorktreeSettings {
|
||||
root: custom_root,
|
||||
auto_cleanup_enabled: false,
|
||||
keep_count: 4,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_settings_reject_invalid_root_and_retention_values() {
|
||||
let codex_home = tempfile::tempdir().expect("create Codex home");
|
||||
let invalid_configs = [
|
||||
HashMap::from([("git-worktree-root".to_owned(), json!("relative/worktrees"))]),
|
||||
HashMap::from([("worktree-auto-cleanup-enabled".to_owned(), json!("yes"))]),
|
||||
HashMap::from([("worktree-keep-count".to_owned(), json!(0))]),
|
||||
HashMap::from([("worktree-keep-count".to_owned(), json!(-1))]),
|
||||
HashMap::from([("worktree-keep-count".to_owned(), json!(1.5))]),
|
||||
];
|
||||
|
||||
for desktop in invalid_configs {
|
||||
assert!(
|
||||
WorktreeSettings::from_desktop_config(codex_home.path(), Some(&desktop)).is_err(),
|
||||
"invalid Desktop worktree settings were accepted: {desktop:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user