diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 9ead5c6e17a..935fc413a58 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -380,7 +380,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -391,7 +391,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1444,6 +1444,7 @@ dependencies = [ "codex-features", "codex-feedback", "codex-file-search", + "codex-git-utils", "codex-login", "codex-otel", "codex-protocol", @@ -1509,6 +1510,7 @@ dependencies = [ "anyhow", "clap", "codex-experimental-api-macros", + "codex-git-utils", "codex-protocol", "codex-utils-absolute-path", "codex-utils-cargo-bin", @@ -1643,7 +1645,7 @@ dependencies = [ "clap", "codex-connectors", "codex-core", - "codex-git", + "codex-git-utils", "codex-utils-cargo-bin", "codex-utils-cli", "pretty_assertions", @@ -1768,6 +1770,7 @@ dependencies = [ "codex-client", "codex-cloud-tasks-client", "codex-core", + "codex-git-utils", "codex-login", "codex-tui", "codex-utils-cli", @@ -1794,7 +1797,7 @@ dependencies = [ "async-trait", "chrono", "codex-backend-client", - "codex-git", + "codex-git-utils", "diffy", "serde", "serde_json", @@ -1879,7 +1882,7 @@ dependencies = [ "codex-execpolicy", "codex-features", "codex-file-search", - "codex-git", + "codex-git-utils", "codex-hooks", "codex-login", "codex-network-proxy", @@ -1993,6 +1996,7 @@ dependencies = [ "codex-cloud-requirements", "codex-core", "codex-feedback", + "codex-git-utils", "codex-otel", "codex-protocol", "codex-utils-absolute-path", @@ -2133,10 +2137,12 @@ dependencies = [ ] [[package]] -name = "codex-git" +name = "codex-git-utils" version = "0.0.0" dependencies = [ "assert_matches", + "codex-utils-absolute-path", + "futures", "once_cell", "pretty_assertions", "regex", @@ -2144,6 +2150,7 @@ dependencies = [ "serde", "tempfile", "thiserror 2.0.18", + "tokio", "ts-rs", "walkdir", ] @@ -2389,7 +2396,7 @@ version = "0.0.0" dependencies = [ "anyhow", "codex-execpolicy", - "codex-git", + "codex-git-utils", "codex-utils-absolute-path", "codex-utils-image", "icu_decimal", @@ -2484,6 +2491,7 @@ dependencies = [ "age", "anyhow", "base64 0.22.1", + "codex-git-utils", "codex-keyring-store", "keyring", "pretty_assertions", @@ -2554,6 +2562,7 @@ dependencies = [ "anyhow", "chrono", "clap", + "codex-git-utils", "codex-protocol", "dirs", "log", @@ -2620,6 +2629,7 @@ dependencies = [ "codex-features", "codex-feedback", "codex-file-search", + "codex-git-utils", "codex-login", "codex-otel", "codex-protocol", @@ -2713,6 +2723,7 @@ dependencies = [ "codex-features", "codex-feedback", "codex-file-search", + "codex-git-utils", "codex-login", "codex-otel", "codex-protocol", @@ -3836,7 +3847,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4081,7 +4092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5526,7 +5537,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6289,7 +6300,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6933,7 +6944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.45.0", + "windows-sys 0.61.2", ] [[package]] @@ -8355,7 +8366,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9788,7 +9799,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.3", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -11234,7 +11245,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index e0c76337337..e071ec807e7 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -50,7 +50,7 @@ members = [ "v8-poc", "utils/absolute-path", "utils/cargo-bin", - "utils/git", + "git-utils", "utils/cache", "utils/image", "utils/json-to-toml", @@ -117,7 +117,7 @@ codex-experimental-api-macros = { path = "codex-experimental-api-macros" } codex-feedback = { path = "feedback" } codex-features = { path = "features" } codex-file-search = { path = "file-search" } -codex-git = { path = "utils/git" } +codex-git-utils = { path = "git-utils" } codex-hooks = { path = "hooks" } codex-keyring-store = { path = "keyring-store" } codex-linux-sandbox = { path = "linux-sandbox" } diff --git a/codex-rs/app-server-protocol/Cargo.toml b/codex-rs/app-server-protocol/Cargo.toml index 8566779a0ae..a9d90831efb 100644 --- a/codex-rs/app-server-protocol/Cargo.toml +++ b/codex-rs/app-server-protocol/Cargo.toml @@ -14,8 +14,9 @@ workspace = true [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } -codex-protocol = { workspace = true } codex-experimental-api-macros = { workspace = true } +codex-git-utils = { workspace = true } +codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/codex-rs/app-server-protocol/src/lib.rs b/codex-rs/app-server-protocol/src/lib.rs index 3c5fa6dc2e4..b1a8f474f3d 100644 --- a/codex-rs/app-server-protocol/src/lib.rs +++ b/codex-rs/app-server-protocol/src/lib.rs @@ -4,6 +4,7 @@ mod jsonrpc_lite; mod protocol; mod schema_fixtures; +pub use codex_git_utils::GitSha; pub use experimental_api::*; pub use export::GenerateTsOptions; pub use export::generate_internal_json_schema; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 56897566d94..d4a8c70dd56 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -14,16 +14,6 @@ use serde::Serialize; use strum_macros::Display; use ts_rs::TS; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)] -#[ts(type = "string")] -pub struct GitSha(pub String); - -impl GitSha { - pub fn new(sha: &str) -> Self { - Self(sha.to_string()) - } -} - /// Authentication mode for OpenAI-backed providers. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)] #[serde(rename_all = "lowercase")] diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 81f3cc58a80..67e552dea3f 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::path::PathBuf; +use codex_git_utils::GitSha; use codex_protocol::ThreadId; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::ReasoningSummary; @@ -21,7 +22,6 @@ use serde::Serialize; use ts_rs::TS; use crate::protocol::common::AuthMode; -use crate::protocol::common::GitSha; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] #[serde(rename_all = "camelCase")] diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index f9e069703e8..1eb09e26057 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -34,6 +34,7 @@ codex-cloud-requirements = { workspace = true } codex-core = { workspace = true } codex-exec-server = { workspace = true } codex-features = { workspace = true } +codex-git-utils = { workspace = true } codex-otel = { workspace = true } codex-shell-command = { workspace = true } codex-utils-cli = { workspace = true } diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index c4c2799ca8d..6fa1510f63b 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -213,7 +213,6 @@ use codex_core::find_archived_thread_path_by_id_str; use codex_core::find_thread_name_by_id; use codex_core::find_thread_names_by_ids; use codex_core::find_thread_path_by_id_str; -use codex_core::git_info::git_diff_to_remote; use codex_core::mcp::auth::discover_supported_scopes; use codex_core::mcp::auth::resolve_oauth_scopes; use codex_core::mcp::collect_mcp_snapshot; @@ -243,6 +242,7 @@ use codex_features::FEATURES; use codex_features::Feature; use codex_features::Stage; use codex_feedback::CodexFeedback; +use codex_git_utils::git_diff_to_remote; use codex_login::ServerOptions as LoginServerOptions; use codex_login::ShutdownHandle; use codex_login::auth::login_with_chatgpt_auth_tokens; @@ -8188,7 +8188,7 @@ fn extract_conversation_summary( fn map_git_info(git_info: &CoreGitInfo) -> ConversationGitInfo { ConversationGitInfo { - sha: git_info.commit_hash.clone(), + sha: git_info.commit_hash.as_ref().map(|sha| sha.0.clone()), branch: git_info.branch.clone(), origin_url: git_info.repository_url.clone(), } diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index 75bffe622cd..2b5d1fe8a02 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -23,6 +23,7 @@ use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::UserInput; use codex_core::ARCHIVED_SESSIONS_SUBDIR; +use codex_git_utils::GitSha; use codex_protocol::ThreadId; use codex_protocol::protocol::GitInfo as CoreGitInfo; use codex_protocol::protocol::RolloutItem; @@ -959,7 +960,7 @@ async fn thread_list_includes_git_info() -> Result<()> { create_minimal_config(codex_home.path())?; let git_info = CoreGitInfo { - commit_hash: Some("abc123".to_string()), + commit_hash: Some(GitSha::new("abc123")), branch: Some("main".to_string()), repository_url: Some("https://example.com/repo.git".to_string()), }; diff --git a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs index 6024fe47f5d..680e70f2d8a 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs @@ -20,6 +20,7 @@ use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::ThreadStatus; use codex_core::ARCHIVED_SESSIONS_SUBDIR; use codex_core::state_db::reconcile_rollout; +use codex_git_utils::GitSha; use codex_protocol::ThreadId; use codex_protocol::protocol::GitInfo as RolloutGitInfo; use codex_state::StateRuntime; @@ -378,7 +379,7 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> { "Thread preview", Some("mock_provider"), Some(RolloutGitInfo { - commit_hash: Some("abc123".to_string()), + commit_hash: Some(GitSha::new("abc123")), branch: Some("feature/sidebar-pr".to_string()), repository_url: Some("git@example.com:openai/codex.git".to_string()), }), diff --git a/codex-rs/chatgpt/Cargo.toml b/codex-rs/chatgpt/Cargo.toml index 17a6f97ad36..cd14a67009d 100644 --- a/codex-rs/chatgpt/Cargo.toml +++ b/codex-rs/chatgpt/Cargo.toml @@ -17,7 +17,7 @@ codex-utils-cargo-bin = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } -codex-git = { workspace = true } +codex-git-utils = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/codex-rs/chatgpt/src/apply_command.rs b/codex-rs/chatgpt/src/apply_command.rs index 6497777a9ff..1a9553955d2 100644 --- a/codex-rs/chatgpt/src/apply_command.rs +++ b/codex-rs/chatgpt/src/apply_command.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; use clap::Parser; use codex_core::config::Config; +use codex_git_utils::ApplyGitRequest; +use codex_git_utils::apply_git_patch; use codex_utils_cli::CliConfigOverrides; use crate::chatgpt_token::init_chatgpt_token_from_auth; @@ -57,13 +59,13 @@ pub async fn apply_diff_from_task( async fn apply_diff(diff: &str, cwd: Option) -> anyhow::Result<()> { let cwd = cwd.unwrap_or(std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir())); - let req = codex_git::ApplyGitRequest { + let req = ApplyGitRequest { cwd, diff: diff.to_string(), revert: false, preflight: false, }; - let res = codex_git::apply_git_patch(&req)?; + let res = apply_git_patch(&req)?; if res.exit_code != 0 { anyhow::bail!( "Git apply failed (applied={}, skipped={}, conflicts={})\nstdout:\n{}\nstderr:\n{}", diff --git a/codex-rs/cloud-tasks-client/Cargo.toml b/codex-rs/cloud-tasks-client/Cargo.toml index 15a206079af..6774d46fdcf 100644 --- a/codex-rs/cloud-tasks-client/Cargo.toml +++ b/codex-rs/cloud-tasks-client/Cargo.toml @@ -25,4 +25,4 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2.0.17" codex-backend-client = { path = "../backend-client", optional = true } -codex-git = { workspace = true } +codex-git-utils = { workspace = true } diff --git a/codex-rs/cloud-tasks-client/src/http.rs b/codex-rs/cloud-tasks-client/src/http.rs index 29211c2e10e..4ea09802273 100644 --- a/codex-rs/cloud-tasks-client/src/http.rs +++ b/codex-rs/cloud-tasks-client/src/http.rs @@ -16,6 +16,8 @@ use chrono::Utc; use codex_backend_client as backend; use codex_backend_client::CodeTaskDetailsResponseExt; +use codex_git_utils::ApplyGitRequest; +use codex_git_utils::apply_git_patch; #[derive(Clone)] pub struct HttpClient { @@ -459,13 +461,13 @@ mod api { }); } - let req = codex_git::ApplyGitRequest { + let req = ApplyGitRequest { cwd: std::env::current_dir().unwrap_or_else(|_| std::env::temp_dir()), diff: diff.clone(), revert: false, preflight, }; - let r = codex_git::apply_git_patch(&req) + let r = apply_git_patch(&req) .map_err(|e| CloudTaskError::Io(format!("git apply failed to run: {e}")))?; let status = if r.exit_code == 0 { diff --git a/codex-rs/cloud-tasks-client/src/lib.rs b/codex-rs/cloud-tasks-client/src/lib.rs index b28b356f2a7..9519050baee 100644 --- a/codex-rs/cloud-tasks-client/src/lib.rs +++ b/codex-rs/cloud-tasks-client/src/lib.rs @@ -27,4 +27,4 @@ pub use mock::MockClient; #[cfg(feature = "online")] pub use http::HttpClient; -// Reusable apply engine now lives in the shared crate `codex-git`. +// Reusable apply engine now lives in the shared crate `codex-git-utils`. diff --git a/codex-rs/cloud-tasks/Cargo.toml b/codex-rs/cloud-tasks/Cargo.toml index a80c455de4a..4ba9968fb0d 100644 --- a/codex-rs/cloud-tasks/Cargo.toml +++ b/codex-rs/cloud-tasks/Cargo.toml @@ -22,6 +22,7 @@ codex-cloud-tasks-client = { path = "../cloud-tasks-client", features = [ ] } codex-client = { workspace = true } codex-core = { path = "../core" } +codex-git-utils = { workspace = true } codex-login = { path = "../login" } codex-tui = { path = "../tui" } codex-utils-cli = { workspace = true } diff --git a/codex-rs/cloud-tasks/src/lib.rs b/codex-rs/cloud-tasks/src/lib.rs index c6abc80c105..7766c1eb8b0 100644 --- a/codex-rs/cloud-tasks/src/lib.rs +++ b/codex-rs/cloud-tasks/src/lib.rs @@ -10,6 +10,8 @@ pub use cli::Cli; use anyhow::anyhow; use chrono::Utc; use codex_cloud_tasks_client::TaskStatus; +use codex_git_utils::current_branch_name; +use codex_git_utils::default_branch_name; use owo_colors::OwoColorize; use owo_colors::Stream; use std::cmp::Ordering; @@ -119,11 +121,11 @@ struct RealGitInfo; #[async_trait::async_trait] impl GitInfoProvider for RealGitInfo { async fn default_branch_name(&self, path: &std::path::Path) -> Option { - codex_core::git_info::default_branch_name(path).await + default_branch_name(path).await } async fn current_branch_name(&self, path: &std::path::Path) -> Option { - codex_core::git_info::current_branch_name(path).await + current_branch_name(path).await } } diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 9c20345170d..82ea2600558 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -41,7 +41,7 @@ codex-shell-command = { workspace = true } codex-skills = { workspace = true } codex-execpolicy = { workspace = true } codex-file-search = { workspace = true } -codex-git = { workspace = true } +codex-git-utils = { workspace = true } codex-hooks = { workspace = true } codex-network-proxy = { workspace = true } codex-otel = { workspace = true } diff --git a/codex-rs/core/src/analytics_client.rs b/codex-rs/core/src/analytics_client.rs index bfb90bc4da9..922f16de58c 100644 --- a/codex-rs/core/src/analytics_client.rs +++ b/codex-rs/core/src/analytics_client.rs @@ -1,9 +1,9 @@ use crate::AuthManager; use crate::config::Config; use crate::default_client::create_client; -use crate::git_info::collect_git_info; -use crate::git_info::get_git_repo_root; use crate::plugins::PluginTelemetryMetadata; +use codex_git_utils::collect_git_info; +use codex_git_utils::get_git_repo_root; use codex_protocol::protocol::SkillScope; use serde::Serialize; use sha1::Digest; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 6165aee5a97..bcadbba2057 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -235,7 +235,6 @@ pub(crate) struct PreviousTurnSettings { use crate::exec_policy::ExecPolicyUpdateError; use crate::feedback_tags; -use crate::git_info::get_git_repo_root; use crate::guardian::GuardianReviewSessionManager; use crate::hook_runtime::PendingInputHookDisposition; use crate::hook_runtime::inspect_pending_input; @@ -350,6 +349,7 @@ use crate::unified_exec::UnifiedExecProcessManager; use crate::util::backoff; use crate::windows_sandbox::WindowsSandboxLevelExt; use codex_async_utils::OrCancelExt; +use codex_git_utils::get_git_repo_root; use codex_otel::SessionTelemetry; use codex_otel::TelemetryAuthMode; use codex_otel::metrics::names::THREAD_STARTED_METRIC; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index dc04d9bb14e..539663c90a7 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -39,7 +39,6 @@ use crate::config_loader::McpServerRequirement; use crate::config_loader::ResidencyRequirement; use crate::config_loader::Sourced; use crate::config_loader::load_config_layers_state; -use crate::git_info::resolve_root_git_project_for_trust; use crate::memories::memory_root; use crate::model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID; use crate::model_provider_info::LMSTUDIO_OSS_PROVIDER_ID; @@ -66,6 +65,7 @@ use codex_features::FeatureConfigSource; use codex_features::FeatureOverrides; use codex_features::Features; use codex_features::FeaturesToml; +use codex_git_utils::resolve_root_git_project_for_trust; use codex_protocol::config_types::AltScreenMode; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::Personality; @@ -134,7 +134,7 @@ pub use service::ConfigService; pub use service::ConfigServiceError; pub use types::ApprovalsReviewer; -pub use codex_git::GhostSnapshotConfig; +pub use codex_git_utils::GhostSnapshotConfig; /// Maximum number of bytes of the documentation that will be embedded. Larger /// files are *silently truncated* to this size so we do not take up too much of diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 0c426b155f7..e7d1e634e81 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -7,10 +7,10 @@ mod tests; use crate::config::ConfigToml; use crate::config_loader::layer_io::LoadedConfigLayers; -use crate::git_info::resolve_root_git_project_for_trust; use codex_app_server_protocol::ConfigLayerSource; use codex_config::CONFIG_TOML_FILE; use codex_config::ConfigRequirementsWithSources; +use codex_git_utils::resolve_root_git_project_for_trust; use codex_protocol::config_types::SandboxMode; use codex_protocol::config_types::TrustLevel; use codex_protocol::protocol::AskForApproval; diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index 0cd680bbd7a..f709afcfff9 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -3,7 +3,7 @@ use crate::truncate; use crate::truncate::TruncationPolicy; use base64::Engine; use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; -use codex_git::GhostCommit; +use codex_git_utils::GhostCommit; use codex_protocol::AgentPath; use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; diff --git a/codex-rs/core/src/git_info_tests.rs b/codex-rs/core/src/git_info_tests.rs index 73714ce42ff..f6f77d1666f 100644 --- a/codex-rs/core/src/git_info_tests.rs +++ b/codex-rs/core/src/git_info_tests.rs @@ -1,9 +1,15 @@ -use super::*; - +use codex_git_utils::GitInfo; +use codex_git_utils::GitSha; +use codex_git_utils::collect_git_info; +use codex_git_utils::get_has_changes; +use codex_git_utils::git_diff_to_remote; +use codex_git_utils::recent_commits; +use codex_git_utils::resolve_root_git_project_for_trust; use core_test_support::skip_if_sandbox; use std::fs; use std::path::PathBuf; use tempfile::TempDir; +use tokio::process::Command; // Helper function to create a test git repository async fn create_test_git_repo(temp_dir: &TempDir) -> PathBuf { @@ -191,7 +197,7 @@ async fn test_collect_git_info_git_repository() { // Should have commit hash assert!(git_info.commit_hash.is_some()); - let commit_hash = git_info.commit_hash.unwrap(); + let commit_hash = git_info.commit_hash.unwrap().0; assert_eq!(commit_hash.len(), 40); // SHA-1 hash should be 40 characters assert!(commit_hash.chars().all(|c| c.is_ascii_hexdigit())); @@ -558,7 +564,7 @@ async fn test_get_git_working_tree_state_unpushed_commit() { #[test] fn test_git_info_serialization() { let git_info = GitInfo { - commit_hash: Some("abc123def456".to_string()), + commit_hash: Some(GitSha::new("abc123def456")), branch: Some("main".to_string()), repository_url: Some("https://github.com/example/repo.git".to_string()), }; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index d3ffdd5657d..decebc84e07 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -41,7 +41,8 @@ mod exec_policy; pub mod external_agent_config; pub mod file_watcher; mod flags; -pub mod git_info; +#[cfg(test)] +mod git_info_tests; mod guardian; mod hook_runtime; pub mod instructions; diff --git a/codex-rs/core/src/plugins/marketplace.rs b/codex-rs/core/src/plugins/marketplace.rs index 17b37f8cc9f..011c1667f5a 100644 --- a/codex-rs/core/src/plugins/marketplace.rs +++ b/codex-rs/core/src/plugins/marketplace.rs @@ -2,9 +2,9 @@ use super::PluginManifestInterface; use super::load_plugin_manifest; use super::store::PluginId; use super::store::PluginIdError; -use crate::git_info::get_git_repo_root; use codex_app_server_protocol::PluginAuthPolicy; use codex_app_server_protocol::PluginInstallPolicy; +use codex_git_utils::get_git_repo_root; use codex_protocol::protocol::Product; use codex_utils_absolute_path::AbsolutePathBuf; use dirs::home_dir; diff --git a/codex-rs/core/src/realtime_context.rs b/codex-rs/core/src/realtime_context.rs index 5e714a83087..bcb24c56739 100644 --- a/codex-rs/core/src/realtime_context.rs +++ b/codex-rs/core/src/realtime_context.rs @@ -1,10 +1,10 @@ use crate::codex::Session; use crate::compact::content_items_to_text; use crate::event_mapping::is_contextual_user_message_content; -use crate::git_info::resolve_root_git_project_for_trust; use crate::truncate::TruncationPolicy; use crate::truncate::truncate_text; use chrono::Utc; +use codex_git_utils::resolve_root_git_project_for_trust; use codex_protocol::models::ResponseItem; use codex_state::SortKey; use codex_state::ThreadMetadata; diff --git a/codex-rs/core/src/review_prompts.rs b/codex-rs/core/src/review_prompts.rs index cfcb7e0b13d..a5bf8e5f997 100644 --- a/codex-rs/core/src/review_prompts.rs +++ b/codex-rs/core/src/review_prompts.rs @@ -1,4 +1,4 @@ -use codex_git::merge_base_with_head; +use codex_git_utils::merge_base_with_head; use codex_protocol::protocol::ReviewRequest; use codex_protocol::protocol::ReviewTarget; use std::path::Path; diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 8a3e41006c6..bf32e23c8d3 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1039,7 +1039,7 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result std::io::Result<()> { - let git_info = collect_git_info(cwd).await; + let git_info = collect_git_info(cwd).await.map(|info| ProtocolGitInfo { + commit_hash: info.commit_hash, + branch: info.branch, + repository_url: info.repository_url, + }); let session_meta_line = SessionMetaLine { meta: session_meta, git: git_info, diff --git a/codex-rs/core/src/tasks/ghost_snapshot.rs b/codex-rs/core/src/tasks/ghost_snapshot.rs index 01aa9758f7d..6028c311ccb 100644 --- a/codex-rs/core/src/tasks/ghost_snapshot.rs +++ b/codex-rs/core/src/tasks/ghost_snapshot.rs @@ -5,10 +5,10 @@ use crate::state::TaskKind; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; use async_trait::async_trait; -use codex_git::CreateGhostCommitOptions; -use codex_git::GhostSnapshotReport; -use codex_git::GitToolingError; -use codex_git::create_ghost_commit_with_report; +use codex_git_utils::CreateGhostCommitOptions; +use codex_git_utils::GhostSnapshotReport; +use codex_git_utils::GitToolingError; +use codex_git_utils::create_ghost_commit_with_report; use codex_protocol::models::ResponseItem; use codex_protocol::user_input::UserInput; use codex_utils_readiness::Readiness; diff --git a/codex-rs/core/src/tasks/ghost_snapshot_tests.rs b/codex-rs/core/src/tasks/ghost_snapshot_tests.rs index 1884a9bd052..68bcbf9f22e 100644 --- a/codex-rs/core/src/tasks/ghost_snapshot_tests.rs +++ b/codex-rs/core/src/tasks/ghost_snapshot_tests.rs @@ -1,5 +1,5 @@ use super::*; -use codex_git::LargeUntrackedDir; +use codex_git_utils::LargeUntrackedDir; use pretty_assertions::assert_eq; use std::path::PathBuf; diff --git a/codex-rs/core/src/tasks/undo.rs b/codex-rs/core/src/tasks/undo.rs index 9d899cb30dc..7fd2ea07e85 100644 --- a/codex-rs/core/src/tasks/undo.rs +++ b/codex-rs/core/src/tasks/undo.rs @@ -8,8 +8,8 @@ use crate::state::TaskKind; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; use async_trait::async_trait; -use codex_git::RestoreGhostCommitOptions; -use codex_git::restore_ghost_commit_with_options; +use codex_git_utils::RestoreGhostCommitOptions; +use codex_git_utils::restore_ghost_commit_with_options; use codex_protocol::models::ResponseItem; use codex_protocol::user_input::UserInput; use tokio_util::sync::CancellationToken; diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index 3a4bac011dd..b3794d3254c 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -8,11 +8,11 @@ use std::sync::RwLock; use serde::Serialize; use tokio::task::JoinHandle; -use crate::git_info::get_git_remote_urls_assume_git_repo; -use crate::git_info::get_git_repo_root; -use crate::git_info::get_has_changes; -use crate::git_info::get_head_commit_hash; use crate::sandbox_tags::sandbox_tag; +use codex_git_utils::get_git_remote_urls_assume_git_repo; +use codex_git_utils::get_git_repo_root; +use codex_git_utils::get_has_changes; +use codex_git_utils::get_head_commit_hash; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::protocol::SandboxPolicy; @@ -94,11 +94,12 @@ fn build_turn_metadata_bag( pub async fn build_turn_metadata_header(cwd: &Path, sandbox: Option<&str>) -> Option { let repo_root = get_git_repo_root(cwd).map(|root| root.to_string_lossy().into_owned()); - let (latest_git_commit_hash, associated_remote_urls, has_changes) = tokio::join!( + let (head_commit_hash, associated_remote_urls, has_changes) = tokio::join!( get_head_commit_hash(cwd), get_git_remote_urls_assume_git_repo(cwd), get_has_changes(cwd), ); + let latest_git_commit_hash = head_commit_hash.map(|sha| sha.0); if latest_git_commit_hash.is_none() && associated_remote_urls.is_none() && has_changes.is_none() @@ -231,11 +232,12 @@ impl TurnMetadataState { } async fn fetch_workspace_git_metadata(&self) -> WorkspaceGitMetadata { - let (latest_git_commit_hash, associated_remote_urls, has_changes) = tokio::join!( + let (head_commit_hash, associated_remote_urls, has_changes) = tokio::join!( get_head_commit_hash(&self.cwd), get_git_remote_urls_assume_git_repo(&self.cwd), get_has_changes(&self.cwd), ); + let latest_git_commit_hash = head_commit_hash.map(|sha| sha.0); WorkspaceGitMetadata { associated_remote_urls, diff --git a/codex-rs/core/tests/suite/cli_stream.rs b/codex-rs/core/tests/suite/cli_stream.rs index 767f8050028..cc8a0103aa2 100644 --- a/codex-rs/core/tests/suite/cli_stream.rs +++ b/codex-rs/core/tests/suite/cli_stream.rs @@ -1,5 +1,6 @@ use assert_cmd::Command as AssertCommand; use codex_core::auth::CODEX_API_KEY_ENV_VAR; +use codex_git_utils::collect_git_info; use codex_protocol::protocol::GitInfo; use codex_utils_cargo_bin::find_resource; use core_test_support::fs_wait; @@ -596,7 +597,7 @@ async fn integration_git_info_unit_test() { .unwrap(); // 3. Test git info collection directly - let git_info = codex_core::git_info::collect_git_info(&git_repo).await; + let git_info = collect_git_info(&git_repo).await; // 4. Verify git info is present and contains expected data assert!(git_info.is_some(), "Git info should be collected"); @@ -608,7 +609,7 @@ async fn integration_git_info_unit_test() { git_info.commit_hash.is_some(), "Git info should contain commit_hash" ); - let commit_hash = git_info.commit_hash.as_ref().unwrap(); + let commit_hash = &git_info.commit_hash.as_ref().unwrap().0; assert_eq!(commit_hash.len(), 40, "Commit hash should be 40 characters"); assert!( commit_hash.chars().all(|c| c.is_ascii_hexdigit()), diff --git a/codex-rs/exec/Cargo.toml b/codex-rs/exec/Cargo.toml index 73fc60c39cb..be9cf5d7504 100644 --- a/codex-rs/exec/Cargo.toml +++ b/codex-rs/exec/Cargo.toml @@ -29,6 +29,7 @@ codex-app-server-protocol = { workspace = true } codex-cloud-requirements = { workspace = true } codex-core = { workspace = true } codex-feedback = { workspace = true } +codex-git-utils = { workspace = true } codex-otel = { workspace = true } codex-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 29ae7bf256e..c4f106b4644 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -64,9 +64,9 @@ use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::LoaderOverrides; use codex_core::config_loader::format_config_error_with_source; use codex_core::format_exec_policy_error_with_source; -use codex_core::git_info::get_git_repo_root; use codex_core::path_utils; use codex_feedback::CodexFeedback; +use codex_git_utils::get_git_repo_root; use codex_otel::set_parent_from_context; use codex_otel::traceparent_context_from_env; use codex_protocol::config_types::SandboxMode; diff --git a/codex-rs/utils/git/BUILD.bazel b/codex-rs/git-utils/BUILD.bazel similarity index 50% rename from codex-rs/utils/git/BUILD.bazel rename to codex-rs/git-utils/BUILD.bazel index 332cdc8d67e..346fd3f8f4c 100644 --- a/codex-rs/utils/git/BUILD.bazel +++ b/codex-rs/git-utils/BUILD.bazel @@ -1,6 +1,6 @@ load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( - name = "git", - crate_name = "codex_git", + name = "git-utils", + crate_name = "codex_git_utils", ) diff --git a/codex-rs/utils/git/Cargo.toml b/codex-rs/git-utils/Cargo.toml similarity index 74% rename from codex-rs/utils/git/Cargo.toml rename to codex-rs/git-utils/Cargo.toml index ef673644a73..7ecc72dfa9c 100644 --- a/codex-rs/utils/git/Cargo.toml +++ b/codex-rs/git-utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "codex-git" +name = "codex-git-utils" version.workspace = true edition.workspace = true license.workspace = true @@ -9,12 +9,15 @@ readme = "README.md" workspace = true [dependencies] +codex-utils-absolute-path = { workspace = true } +futures = { workspace = true, features = ["alloc"] } once_cell = { workspace = true } regex = "1" schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } tempfile = { workspace = true } thiserror = { workspace = true } +tokio = { workspace = true, features = ["macros", "process", "rt", "time"] } ts-rs = { workspace = true, features = [ "uuid-impl", "serde-json-impl", diff --git a/codex-rs/utils/git/README.md b/codex-rs/git-utils/README.md similarity index 95% rename from codex-rs/utils/git/README.md rename to codex-rs/git-utils/README.md index 5bcc6dcc335..bd24b72b7a5 100644 --- a/codex-rs/utils/git/README.md +++ b/codex-rs/git-utils/README.md @@ -1,4 +1,4 @@ -# codex-git +# codex-git-utils Helpers for interacting with git, including patch application and worktree snapshot utilities. @@ -6,7 +6,7 @@ snapshot utilities. ```rust,no_run use std::path::Path; -use codex_git::{ +use codex_git_utils::{ apply_git_patch, create_ghost_commit, restore_ghost_commit, ApplyGitRequest, CreateGhostCommitOptions, }; diff --git a/codex-rs/utils/git/src/apply.rs b/codex-rs/git-utils/src/apply.rs similarity index 100% rename from codex-rs/utils/git/src/apply.rs rename to codex-rs/git-utils/src/apply.rs diff --git a/codex-rs/utils/git/src/branch.rs b/codex-rs/git-utils/src/branch.rs similarity index 100% rename from codex-rs/utils/git/src/branch.rs rename to codex-rs/git-utils/src/branch.rs diff --git a/codex-rs/utils/git/src/errors.rs b/codex-rs/git-utils/src/errors.rs similarity index 100% rename from codex-rs/utils/git/src/errors.rs rename to codex-rs/git-utils/src/errors.rs diff --git a/codex-rs/utils/git/src/ghost_commits.rs b/codex-rs/git-utils/src/ghost_commits.rs similarity index 100% rename from codex-rs/utils/git/src/ghost_commits.rs rename to codex-rs/git-utils/src/ghost_commits.rs diff --git a/codex-rs/core/src/git_info.rs b/codex-rs/git-utils/src/info.rs similarity index 96% rename from codex-rs/core/src/git_info.rs rename to codex-rs/git-utils/src/info.rs index 052f786bfab..06e39b56552 100644 --- a/codex-rs/core/src/git_info.rs +++ b/codex-rs/git-utils/src/info.rs @@ -4,15 +4,17 @@ use std::ffi::OsStr; use std::path::Path; use std::path::PathBuf; -use crate::util::resolve_path; -use codex_app_server_protocol::GitSha; -use codex_protocol::protocol::GitInfo; +use codex_utils_absolute_path::AbsolutePathBuf; use futures::future::join_all; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use tokio::process::Command; use tokio::time::Duration as TokioDuration; use tokio::time::timeout; +use ts_rs::TS; + +use crate::GitSha; /// Return `true` if the project folder specified by the `Config` is inside a /// Git repository. @@ -38,6 +40,19 @@ pub fn get_git_repo_root(base_dir: &Path) -> Option { /// Timeout for git commands to prevent freezing on large repositories const GIT_COMMAND_TIMEOUT: TokioDuration = TokioDuration::from_secs(5); +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, TS)] +pub struct GitInfo { + /// Current commit hash (SHA) + #[serde(skip_serializing_if = "Option::is_none")] + pub commit_hash: Option, + /// Current branch name + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Repository URL (if available from remote) + #[serde(skip_serializing_if = "Option::is_none")] + pub repository_url: Option, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GitDiffToRemote { pub sha: GitSha, @@ -77,7 +92,7 @@ pub async fn collect_git_info(cwd: &Path) -> Option { && output.status.success() && let Ok(hash) = String::from_utf8(output.stdout) { - git_info.commit_hash = Some(hash.trim().to_string()); + git_info.commit_hash = Some(GitSha::new(hash.trim())); } // Process branch name @@ -127,7 +142,7 @@ pub async fn get_git_remote_urls_assume_git_repo(cwd: &Path) -> Option Option { +pub async fn get_head_commit_hash(cwd: &Path) -> Option { let output = run_git_command_with_timeout(&["rev-parse", "HEAD"], cwd).await?; if !output.status.success() { return None; @@ -138,7 +153,7 @@ pub async fn get_head_commit_hash(cwd: &Path) -> Option { if hash.is_empty() { None } else { - Some(hash.to_string()) + Some(GitSha::new(hash)) } } @@ -616,7 +631,11 @@ pub fn resolve_root_git_project_for_trust(cwd: &Path) -> Option { return None; } - let git_dir_path = canonicalize_or_raw(resolve_path(&repo_root, &PathBuf::from(git_dir_rel))); + let git_dir_path = canonicalize_or_raw( + AbsolutePathBuf::resolve_path_against_base(git_dir_rel, &repo_root) + .ok()? + .into_path_buf(), + ); let worktrees_dir = git_dir_path.parent()?; if worktrees_dir.file_name() != Some(OsStr::new("worktrees")) { return None; @@ -689,7 +708,3 @@ pub async fn current_branch_name(cwd: &Path) -> Option { .map(|s| s.trim().to_string()) .filter(|name| !name.is_empty()) } - -#[cfg(test)] -#[path = "git_info_tests.rs"] -mod tests; diff --git a/codex-rs/utils/git/src/lib.rs b/codex-rs/git-utils/src/lib.rs similarity index 77% rename from codex-rs/utils/git/src/lib.rs rename to codex-rs/git-utils/src/lib.rs index c4db10fc69c..8f188e4bf0f 100644 --- a/codex-rs/utils/git/src/lib.rs +++ b/codex-rs/git-utils/src/lib.rs @@ -5,6 +5,7 @@ mod apply; mod branch; mod errors; mod ghost_commits; +mod info; mod operations; mod platform; @@ -28,6 +29,21 @@ pub use ghost_commits::create_ghost_commit_with_report; pub use ghost_commits::restore_ghost_commit; pub use ghost_commits::restore_ghost_commit_with_options; pub use ghost_commits::restore_to_commit; +pub use info::CommitLogEntry; +pub use info::GitDiffToRemote; +pub use info::GitInfo; +pub use info::collect_git_info; +pub use info::current_branch_name; +pub use info::default_branch_name; +pub use info::get_git_remote_urls; +pub use info::get_git_remote_urls_assume_git_repo; +pub use info::get_git_repo_root; +pub use info::get_has_changes; +pub use info::get_head_commit_hash; +pub use info::git_diff_to_remote; +pub use info::local_git_branches; +pub use info::recent_commits; +pub use info::resolve_root_git_project_for_trust; pub use platform::create_symlink; use schemars::JsonSchema; use serde::Deserialize; @@ -36,6 +52,17 @@ use ts_rs::TS; type CommitID = String; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)] +#[serde(transparent)] +#[ts(type = "string")] +pub struct GitSha(pub String); + +impl GitSha { + pub fn new(sha: &str) -> Self { + Self(sha.to_string()) + } +} + /// Details of a ghost commit created from a repository state. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)] pub struct GhostCommit { diff --git a/codex-rs/utils/git/src/operations.rs b/codex-rs/git-utils/src/operations.rs similarity index 100% rename from codex-rs/utils/git/src/operations.rs rename to codex-rs/git-utils/src/operations.rs diff --git a/codex-rs/utils/git/src/platform.rs b/codex-rs/git-utils/src/platform.rs similarity index 100% rename from codex-rs/utils/git/src/platform.rs rename to codex-rs/git-utils/src/platform.rs diff --git a/codex-rs/protocol/Cargo.toml b/codex-rs/protocol/Cargo.toml index 11efa9d3767..eaaf791bb8c 100644 --- a/codex-rs/protocol/Cargo.toml +++ b/codex-rs/protocol/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] codex-execpolicy = { workspace = true } -codex-git = { workspace = true } +codex-git-utils = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-image = { workspace = true } icu_decimal = { workspace = true } diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 5c68d3c3e35..6fa8162bf75 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -23,7 +23,7 @@ use crate::protocol::SandboxPolicy; use crate::protocol::WritableRoot; use crate::user_input::UserInput; use codex_execpolicy::Policy; -use codex_git::GhostCommit; +use codex_git_utils::GhostCommit; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_image::error::ImageProcessingError; use schemars::JsonSchema; diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 54a9990fa0c..a2f181715b9 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -49,6 +49,7 @@ use crate::request_permissions::RequestPermissionsEvent; use crate::request_permissions::RequestPermissionsResponse; use crate::request_user_input::RequestUserInputResponse; use crate::user_input::UserInput; +use codex_git_utils::GitSha; use codex_utils_absolute_path::AbsolutePathBuf; use schemars::JsonSchema; use serde::Deserialize; @@ -2636,7 +2637,7 @@ pub struct RolloutLine { pub struct GitInfo { /// Current commit hash (SHA) #[serde(skip_serializing_if = "Option::is_none")] - pub commit_hash: Option, + pub commit_hash: Option, /// Current branch name #[serde(skip_serializing_if = "Option::is_none")] pub branch: Option, diff --git a/codex-rs/secrets/Cargo.toml b/codex-rs/secrets/Cargo.toml index eae8f9005ed..7ca634b251e 100644 --- a/codex-rs/secrets/Cargo.toml +++ b/codex-rs/secrets/Cargo.toml @@ -11,6 +11,7 @@ workspace = true age = { workspace = true } anyhow = { workspace = true } base64 = { workspace = true } +codex-git-utils = { workspace = true } codex-keyring-store = { workspace = true } rand = { workspace = true } regex = { workspace = true } diff --git a/codex-rs/secrets/src/lib.rs b/codex-rs/secrets/src/lib.rs index 1ec925fd4f6..fe482f7d21d 100644 --- a/codex-rs/secrets/src/lib.rs +++ b/codex-rs/secrets/src/lib.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::sync::Arc; use anyhow::Result; +use codex_git_utils::get_git_repo_root; use codex_keyring_store::DefaultKeyringStore; use codex_keyring_store::KeyringStore; use schemars::JsonSchema; @@ -161,22 +162,6 @@ pub fn environment_id_from_cwd(cwd: &Path) -> String { format!("cwd-{short}") } -fn get_git_repo_root(base_dir: &Path) -> Option { - let mut dir = base_dir.to_path_buf(); - - loop { - if dir.join(".git").exists() { - return Some(dir); - } - - if !dir.pop() { - break; - } - } - - None -} - pub(crate) fn compute_keyring_account(codex_home: &Path) -> String { let canonical = codex_home .canonicalize() diff --git a/codex-rs/state/Cargo.toml b/codex-rs/state/Cargo.toml index bb80f60e328..a3b43f81d75 100644 --- a/codex-rs/state/Cargo.toml +++ b/codex-rs/state/Cargo.toml @@ -22,6 +22,7 @@ tracing-subscriber = { workspace = true } uuid = { workspace = true } [dev-dependencies] +codex-git-utils = { workspace = true } pretty_assertions = { workspace = true } [lints] diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 833938800f2..8d35d393a89 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -61,7 +61,7 @@ fn apply_session_meta_from_item(metadata: &mut ThreadMetadata, meta_line: &Sessi metadata.cwd = meta_line.meta.cwd.clone(); } if let Some(git) = meta_line.git.as_ref() { - metadata.git_sha = git.commit_hash.clone(); + metadata.git_sha = git.commit_hash.as_ref().map(|sha| sha.0.clone()); metadata.git_branch = git.branch.clone(); metadata.git_origin_url = git.repository_url.clone(); } diff --git a/codex-rs/state/src/runtime/threads.rs b/codex-rs/state/src/runtime/threads.rs index 0972f9d1fdb..e3312c87d67 100644 --- a/codex-rs/state/src/runtime/threads.rs +++ b/codex-rs/state/src/runtime/threads.rs @@ -1089,7 +1089,7 @@ mod tests { memory_mode: None, }, git: Some(GitInfo { - commit_hash: Some("rollout-sha".to_string()), + commit_hash: Some(codex_git_utils::GitSha::new("rollout-sha")), branch: Some("rollout-branch".to_string()), repository_url: Some("git@example.com:openai/codex.git".to_string()), }), diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 8013b1325ed..01a9c27a7ae 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -40,6 +40,7 @@ codex-core = { workspace = true } codex-features = { workspace = true } codex-feedback = { workspace = true } codex-file-search = { workspace = true } +codex-git-utils = { workspace = true } codex-login = { workspace = true } codex-otel = { workspace = true } codex-protocol = { workspace = true } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 9963b95700b..4c3c30e81cd 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -69,9 +69,6 @@ use codex_core::config::types::Notifications; use codex_core::config::types::WindowsSandboxModeToml; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::find_thread_name_by_id; -use codex_core::git_info::current_branch_name; -use codex_core::git_info::get_git_repo_root; -use codex_core::git_info::local_git_branches; use codex_core::mcp::McpManager; use codex_core::models_manager::manager::ModelsManager; use codex_core::plugins::PluginsManager; @@ -81,6 +78,12 @@ use codex_core::skills::model::SkillMetadata; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_features::FEATURES; use codex_features::Feature; +#[cfg(test)] +use codex_git_utils::CommitLogEntry; +use codex_git_utils::current_branch_name; +use codex_git_utils::get_git_repo_root; +use codex_git_utils::local_git_branches; +use codex_git_utils::recent_commits; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -9246,7 +9249,7 @@ impl ChatWidget { } pub(crate) async fn show_review_commit_picker(&mut self, cwd: &Path) { - let commits = codex_core::git_info::recent_commits(cwd, /*limit*/ 100).await; + let commits = recent_commits(cwd, /*limit*/ 100).await; let mut items: Vec = Vec::with_capacity(commits.len()); for entry in commits { @@ -9648,7 +9651,7 @@ async fn fetch_rate_limits(base_url: String, auth: CodexAuth) -> Vec, + entries: Vec, ) { let mut items: Vec = Vec::with_capacity(entries.len()); for entry in entries { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 78bb4cf22da..04c3fd73d60 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -51,6 +51,7 @@ use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME; use codex_core::skills::model::SkillMetadata; use codex_features::FEATURES; use codex_features::Feature; +use codex_git_utils::CommitLogEntry; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -6725,12 +6726,12 @@ async fn review_commit_picker_shows_subjects_without_timestamps() { // Show commit picker with synthetic entries. let entries = vec![ - codex_core::git_info::CommitLogEntry { + CommitLogEntry { sha: "1111111deadbeef".to_string(), timestamp: 0, subject: "Add new feature X".to_string(), }, - codex_core::git_info::CommitLogEntry { + CommitLogEntry { sha: "2222222cafebabe".to_string(), timestamp: 0, subject: "Fix bug Y".to_string(), diff --git a/codex-rs/tui/src/diff_render.rs b/codex-rs/tui/src/diff_render.rs index 684d76b1e08..ede36abf961 100644 --- a/codex-rs/tui/src/diff_render.rs +++ b/codex-rs/tui/src/diff_render.rs @@ -92,7 +92,7 @@ use crate::terminal_palette::default_bg; use crate::terminal_palette::indexed_color; use crate::terminal_palette::rgb_color; use crate::terminal_palette::stdout_color_level; -use codex_core::git_info::get_git_repo_root; +use codex_git_utils::get_git_repo_root; use codex_protocol::protocol::FileChange; use codex_terminal_detection::TerminalName; use codex_terminal_detection::terminal_info; diff --git a/codex-rs/tui/src/onboarding/trust_directory.rs b/codex-rs/tui/src/onboarding/trust_directory.rs index 06c73cc2db7..66add9315f9 100644 --- a/codex-rs/tui/src/onboarding/trust_directory.rs +++ b/codex-rs/tui/src/onboarding/trust_directory.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use codex_core::config::set_project_trust_level; -use codex_core::git_info::resolve_root_git_project_for_trust; +use codex_git_utils::resolve_root_git_project_for_trust; use codex_protocol::config_types::TrustLevel; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; diff --git a/codex-rs/tui_app_server/Cargo.toml b/codex-rs/tui_app_server/Cargo.toml index 88660420517..b05b498ac83 100644 --- a/codex-rs/tui_app_server/Cargo.toml +++ b/codex-rs/tui_app_server/Cargo.toml @@ -44,6 +44,7 @@ codex-core = { workspace = true } codex-features = { workspace = true } codex-feedback = { workspace = true } codex-file-search = { workspace = true } +codex-git-utils = { workspace = true } codex-login = { workspace = true } codex-otel = { workspace = true } codex-protocol = { workspace = true } diff --git a/codex-rs/tui_app_server/src/chatwidget.rs b/codex-rs/tui_app_server/src/chatwidget.rs index 95e5f27a771..b444dc13bc6 100644 --- a/codex-rs/tui_app_server/src/chatwidget.rs +++ b/codex-rs/tui_app_server/src/chatwidget.rs @@ -89,9 +89,6 @@ use codex_core::config::types::Notifications; use codex_core::config::types::WindowsSandboxModeToml; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::find_thread_name_by_id; -use codex_core::git_info::current_branch_name; -use codex_core::git_info::get_git_repo_root; -use codex_core::git_info::local_git_branches; use codex_core::plugins::PluginsManager; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::skills::model::SkillMetadata; @@ -99,6 +96,12 @@ use codex_core::skills::model::SkillMetadata; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_features::FEATURES; use codex_features::Feature; +#[cfg(test)] +use codex_git_utils::CommitLogEntry; +use codex_git_utils::current_branch_name; +use codex_git_utils::get_git_repo_root; +use codex_git_utils::local_git_branches; +use codex_git_utils::recent_commits; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -10414,7 +10417,7 @@ impl ChatWidget { } pub(crate) async fn show_review_commit_picker(&mut self, cwd: &Path) { - let commits = codex_core::git_info::recent_commits(cwd, /*limit*/ 100).await; + let commits = recent_commits(cwd, /*limit*/ 100).await; let mut items: Vec = Vec::with_capacity(commits.len()); for entry in commits { @@ -10796,7 +10799,7 @@ fn hook_event_label(event_name: codex_protocol::protocol::HookEventName) -> &'st #[cfg(test)] pub(crate) fn show_review_commit_picker_with_entries( chat: &mut ChatWidget, - entries: Vec, + entries: Vec, ) { let mut items: Vec = Vec::with_capacity(entries.len()); for entry in entries { diff --git a/codex-rs/tui_app_server/src/chatwidget/tests.rs b/codex-rs/tui_app_server/src/chatwidget/tests.rs index 3e73b4455c1..3412b22a601 100644 --- a/codex-rs/tui_app_server/src/chatwidget/tests.rs +++ b/codex-rs/tui_app_server/src/chatwidget/tests.rs @@ -74,6 +74,7 @@ use codex_core::plugins::OPENAI_CURATED_MARKETPLACE_NAME; use codex_core::skills::model::SkillMetadata; use codex_features::FEATURES; use codex_features::Feature; +use codex_git_utils::CommitLogEntry; use codex_otel::RuntimeMetricsSummary; use codex_otel::SessionTelemetry; use codex_protocol::ThreadId; @@ -7322,12 +7323,12 @@ async fn review_commit_picker_shows_subjects_without_timestamps() { // Show commit picker with synthetic entries. let entries = vec![ - codex_core::git_info::CommitLogEntry { + CommitLogEntry { sha: "1111111deadbeef".to_string(), timestamp: 0, subject: "Add new feature X".to_string(), }, - codex_core::git_info::CommitLogEntry { + CommitLogEntry { sha: "2222222cafebabe".to_string(), timestamp: 0, subject: "Fix bug Y".to_string(), diff --git a/codex-rs/tui_app_server/src/diff_render.rs b/codex-rs/tui_app_server/src/diff_render.rs index 684d76b1e08..ede36abf961 100644 --- a/codex-rs/tui_app_server/src/diff_render.rs +++ b/codex-rs/tui_app_server/src/diff_render.rs @@ -92,7 +92,7 @@ use crate::terminal_palette::default_bg; use crate::terminal_palette::indexed_color; use crate::terminal_palette::rgb_color; use crate::terminal_palette::stdout_color_level; -use codex_core::git_info::get_git_repo_root; +use codex_git_utils::get_git_repo_root; use codex_protocol::protocol::FileChange; use codex_terminal_detection::TerminalName; use codex_terminal_detection::terminal_info; diff --git a/codex-rs/tui_app_server/src/onboarding/trust_directory.rs b/codex-rs/tui_app_server/src/onboarding/trust_directory.rs index 06c73cc2db7..66add9315f9 100644 --- a/codex-rs/tui_app_server/src/onboarding/trust_directory.rs +++ b/codex-rs/tui_app_server/src/onboarding/trust_directory.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use codex_core::config::set_project_trust_level; -use codex_core::git_info::resolve_root_git_project_for_trust; +use codex_git_utils::resolve_root_git_project_for_trust; use codex_protocol::config_types::TrustLevel; use crossterm::event::KeyCode; use crossterm::event::KeyEvent;